'use strict';
var Settings = {};
Settings.grid = {
    numZoneCols: 10,
    numZoneRows: 10
};
Settings.region = {
    numRegionColsInZone: 6,
    numRegionRowsInZone: 6,
    numCellColsInRegion: 10,
    numCellRowsInRegion: 10
};
Settings.cell = {
    width: 250,
    height: 250,
    maxCountBase: 25,
    maxCountPerRowIncrement: 3,
    maxCountPerColIncrement: 2,
    neighborUnlockCount: 10
};
Settings.balance = {
    baseRegionCostFactor: 1.0017,
    baseRegionMoneyFactor: 1.0014,
    regionCostFactorPerCol: 0.00228,
    regionMoneyFactorPerCol: 0.001046,
    cellCostPenaltyPerRegionIncrement: 2,
    cellCostDistanceDivisor: 5 * Settings.cell.width,
    columnBonusMultiplier: 1.15,
    rowBonusMultiplier: 1.15,
    columnBonusDisplay: "15%",
    rowBonusDisplay: "15%",
    tickBonusPerVictory: 20,
    settings: {
        zoneCellDistancePenaltyPerIncrement: 20,
        prestigeCellDistancePenalty: 80,
        zoneCostFactorPerIncrement: 0.015,
        zoneMoneyFactorPerIncrement: 0.00465
    }
};
Settings.tick = {
    initialMillisPerTick: 750,
    prestigeFactor: 0.993,
    minimumMillisPerTick: 1E-200
};
Settings.input = {
    panning: {
        decayFactor: 0.95
    }
};
Settings.projection = {
    startCenterX: 0,
    startCenterY: 0,
    zoomInScaleLimit: 1,
    zoomOutScaleLimit: 0.003,
    zoomStep: 0.85
};
Settings.total = {
    initialMoney: 3
};
Settings.save = {
    autoSavePeriod: 1E3 * 60 * 5
};
Settings.autoPurchase = {
    regionFocusMillis: 1E3
};
Settings.colors = {
    victoryColor: "#FF00FF",
    cellVictoryColor: "#0000CC",
    evenColors: {
        locked: "#777777",
        filled: "#00AA00",
        prestige: "#DD4400",
        prestigeVeteran: "#008800",
        secondPrestigeVeteran: "#006600",
        activePrestige: "#FF6633",
        active: "#5577FF"
    },
    oddColors: {
        locked: "#777777",
        filled: "#8844CC",
        prestige: "#DD4400",
        prestigeVeteran: "#551188",
        secondPrestigeVeteran: "#330066",
        activePrestige: "#FF6633",
        active: "#008888"
    },
    bonusRegion: {
        locked: "#777777",
        filled: "#DD0000",
        prestige: "#DD4400",
        prestigeVeteran: "#550000",
        secondPrestigeVeteran: "#330000",
        activePrestige: "#FF6633",
        active: "#770000"
    }
};
var Time = {
    getCurrentTime: function () {
        if (Date.now) return Date.now();
        else return (new Date).valueOf()
    }
};
var Cast = {
    toInt: function (value) {
        if (value < 0) return -Cast.toInt(-value);
        if (value < 2147483648) return value | 0;
        else return Math.floor(value)
    }
};
var EventTracker = {
    trackEvent: function (category, action) {
        if (typeof ganal != "undefined") ganal("send", "event", category, action)
    }
};
var Formatter = {};
Formatter.format = function (n) {
    if (n < 1E4) return "" + Cast.toInt(n);
    else return n.toExponential(2)
};
Formatter.formatElapsedTime = function (milliseconds) {
    if (milliseconds < 0) return "Error: negative time";
    var millisPerHour = 1E3 * 60 * 60,
        hours = Cast.toInt(milliseconds / millisPerHour),
        minutes = Cast.toInt(milliseconds / 6E4 % 60),
        seconds = Cast.toInt(milliseconds / 1E3 % 60);
    return (hours < 10 ? "0" : "") + hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds
};

function Vector(x, y) {
    this.x = x;
    this.y = y
}
Vector.prototype.getX = function () {
    return this.x
};
Vector.prototype.getY = function () {
    return this.y
};
Vector.prototype.set = function (x, y) {
    this.x = x;
    this.y = y
};
Vector.prototype.copy = function (vector) {
    this.x = vector.x;
    this.y = vector.y
};
Vector.prototype.add = function (vector) {
    this.x += vector.x;
    this.y += vector.y
};
Vector.prototype.addXY = function (x, y) {
    this.x += x;
    this.y += y
};
Vector.prototype.subtract = function (vector) {
    this.x -= vector.x;
    this.y -= vector.y
};
Vector.prototype.scale = function (scalar) {
    this.x *= scalar;
    this.y *= scalar
};
Vector.prototype.distance = function (vector) {
    var x = vector.x - this.x,
        y = vector.y - this.y;
    return Math.sqrt(x * x + y * y)
};
Vector.prototype.length = function () {
    return Math.sqrt(this.x * this.x + this.y * this.y)
};

function UserInput() {
    this.firstMovement = true;
    this.leftMouseDown = false;
    this.prevMouseX = 0;
    this.prevMouseY = 0;
    this.mouseX = 0;
    this.mouseY = 0;
    this.mouseDeltaX = 0;
    this.mouseDeltaY = 0;
    this.mouseDownTime = 0;
    this.leftMouseClick = false;
    this.mouseWheelMotion = 0;
    this.mouseMoved = false
}
UserInput.prototype.attachListeners = function (document, element) {
    var thisRef = this;
    element.onmouseover = function (event) {
        element.focus();
        thisRef.mouseX = event.clientX;
        thisRef.mouseY = event.clientY;
        return false
    };
    element.addEventListener("mousewheel", function (event) {
        thisRef.mouseWheelMotion = -(event.wheelDelta || event.detail);
        return false
    }, false);
    element.addEventListener("DOMMouseScroll", function (event) {
        thisRef.mouseWheelMotion = event.wheelDelta || event.detail;
        return false
    }, false);
    element.onmouseout = function (event) {
        thisRef.leftMouseDown =
            false;
        thisRef.mouseX = event.clientX;
        thisRef.mouseY = event.clientY
    };
    if (document) document.onmouseout = function (event) {
        thisRef.leftMouseDown = false;
        thisRef.mouseX = event.clientX;
        thisRef.mouseY = event.clientY
    };
    element.onmousedown = function (event) {
        thisRef.mouseX = event.clientX;
        thisRef.mouseY = event.clientY;
        thisRef.prevMouseX = event.clientX;
        thisRef.prevMouseY = event.clientY;
        thisRef.mouseMoved = false;
        thisRef.mouseDeltaX = 0;
        thisRef.mouseDeltaY = 0;
        thisRef.mouseDownTime = Date.now();
        if (event.button === 0 || event.button === 1) thisRef.leftMouseDown =
            true;
        return false
    };
    element.onmouseup = function (event) {
        thisRef.mouseX = event.clientX;
        thisRef.mouseY = event.clientY;
        thisRef.mouseDeltaX = thisRef.mouseX - thisRef.prevMouseX;
        thisRef.mouseDeltaY = thisRef.mouseY - thisRef.prevMouseY;
        thisRef.prevMouseX = thisRef.mouseX;
        thisRef.prevMouseY = thisRef.mouseY;
        if (event.button === 0 || event.button === 1) {
            thisRef.leftMouseDown = false;
            if (!thisRef.mouseMoved && Date.now() - thisRef.mouseDownTime < 250) thisRef.leftMouseClick = true
        }
        return false
    };
    element.onmousemove = function (event) {
        thisRef.mouseX =
            event.clientX;
        thisRef.mouseY = event.clientY;
        thisRef.mouseMoved = true;
        if (thisRef.firstMovement) thisRef.firstMovement = false;
        else {
            thisRef.mouseDeltaX += thisRef.mouseX - thisRef.prevMouseX;
            thisRef.mouseDeltaY += thisRef.mouseY - thisRef.prevMouseY
        }
        thisRef.prevMouseX = thisRef.mouseX;
        thisRef.prevMouseY = thisRef.mouseY;
        return false
    }
};
UserInput.prototype.isLeftMouseDown = function () {
    return this.leftMouseDown
};
UserInput.prototype.getMouseDeltaX = function () {
    return this.mouseDeltaX
};
UserInput.prototype.getMouseDeltaY = function () {
    return this.mouseDeltaY
};
UserInput.prototype.getMouseX = function () {
    return this.mouseX
};
UserInput.prototype.getMouseY = function () {
    return this.mouseY
};
UserInput.prototype.isLeftMouseClick = function () {
    return this.leftMouseClick
};
UserInput.prototype.getMouseWheelMotion = function () {
    return this.mouseWheelMotion
};
UserInput.prototype.resetInputState = function () {
    this.leftMouseClick = false;
    this.mouseWheelMotion = 0;
    this.mouseDeltaX = 0;
    this.mouseDeltaY = 0;
    this.mouseMoved = false
};

function WorldMouse() {
    this.worldPosition = new Vector(0, 0);
    this.screenPosition = new Vector(0, 0)
}
WorldMouse.prototype.updateWorldMouseForFrame = function () {
    this.screenPosition.set(Game.userInput.getMouseX(), Game.userInput.getMouseY());
    Game.projection.screenToWorld(this.screenPosition, this.worldPosition)
};
WorldMouse.prototype.getWorldPosition = function () {
    return this.worldPosition
};

function Projection() {
    this.gridCenter = new Vector(0, 0);
    this.screenOrigin = new Vector(0, 0);
    this.visibleHalfWidth = 0;
    this.visibleHalfHeight = 0;
    this.zoom = Settings.projection.zoomInScaleLimit
}
Projection.prototype.resetProjectionState = function () {
    this.setGridCenter(Settings.projection.startCenterX, Settings.projection.startCenterY);
    this.zoom = Settings.projection.zoomInScaleLimit
};
Projection.prototype.setGridCenter = function (worldX, worldY) {
    if (this.gridCenter.x != worldX || this.gridCenter.y != worldY) this.gridCenter.set(worldX, worldY)
};
Projection.prototype.updateSpaceCenter = function (deltaX, deltaY) {
    this.gridCenter.addXY(deltaX, deltaY);
    var zoneWidth = Settings.region.numRegionColsInZone * Settings.region.numCellColsInRegion * Settings.cell.width,
        zoneHeight = Settings.region.numRegionRowsInZone * Settings.region.numCellRowsInRegion * Settings.cell.height,
        zoneCols = Game.grid.getGridCols(),
        zoneRows = Game.grid.getGridRows(),
        gridRightX = zoneCols * zoneWidth,
        gridBottomY = zoneRows * zoneHeight;
    var leftLimitX = -2 * zoneWidth,
        topLimitY = -2 * zoneHeight,
        rightLimitX =
        gridRightX + 2 * zoneWidth,
        bottomLimitY = gridBottomY + 2 * zoneHeight;
    if (this.gridCenter.x < leftLimitX) this.gridCenter.x = leftLimitX;
    if (this.gridCenter.y < topLimitY) this.gridCenter.y = topLimitY;
    if (this.gridCenter.x > rightLimitX) this.gridCenter.x = rightLimitX;
    if (this.gridCenter.y > bottomLimitY) this.gridCenter.y = bottomLimitY
};
Projection.prototype.updateProjectionForFrame = function () {
    this.visibleHalfWidth = Game.canvas.width / 2;
    this.visibleHalfHeight = Game.canvas.height / 2;
    this.screenOrigin.set(this.zoom * this.gridCenter.x - this.visibleHalfWidth, this.zoom * this.gridCenter.y - this.visibleHalfHeight)
};
Projection.prototype.screenToWorldDistance = function (screenDistance) {
    return screenDistance / this.zoom
};
Projection.prototype.screenToWorld = function (screenCoordinate, worldCoordinate) {
    var factor = 1 / this.zoom;
    worldCoordinate.set(factor * (this.screenOrigin.getX() + screenCoordinate.getX()), factor * (this.screenOrigin.getY() + screenCoordinate.getY()))
};
Projection.prototype.worldToScreen = function (worldCoordinate, screenCoordinate) {
    screenCoordinate.x = Cast.toInt(this.zoom * worldCoordinate.x) - this.screenOrigin.x;
    screenCoordinate.y = Cast.toInt(this.zoom * worldCoordinate.y) - this.screenOrigin.y
};
Projection.prototype.getGridCenter = function () {
    return this.gridCenter
};
Projection.prototype.getScreenOrigin = function () {
    return this.screenOrigin
};
Projection.prototype.zoomOut = function () {
    this.zoom *= Settings.projection.zoomStep;
    if (this.zoom < Settings.projection.zoomOutScaleLimit) this.zoom = Settings.projection.zoomOutScaleLimit
};
Projection.prototype.zoomIn = function () {
    this.zoom /= Settings.projection.zoomStep;
    if (this.zoom > Settings.projection.zoomInScaleLimit) this.zoom = Settings.projection.zoomInScaleLimit
};
Projection.prototype.getZoom = function () {
    return this.zoom
};

function Model() {}
Model.prototype.onSaveLoad = function () {};
Model.prototype.resetState = function () {};

function BaseModel() {}
BaseModel.prototype.onSaveLoad = function () {};
BaseModel.prototype.resetState = function () {};
var CountEnum = {
    Purchase1: 1,
    PurchaseMax: -100
};

function Tick() {
    this.millisPerTick = Settings.tick.initialMillisPerTick;
    this.lastTickTime = Time.getCurrentTime();
    this.regionPrestigeCount = 0
}
Tick.prototype = new BaseModel;
Tick.prototype.resetState = function () {
    this.millisPerTick = Settings.tick.initialMillisPerTick;
    this.lastTickTime = Time.getCurrentTime();
    this.regionPrestigeCount = 0
};
Tick.prototype.onSaveLoad = function () {
    this.millisPerTick = Settings.tick.initialMillisPerTick;
    var i, prestigeBonuses = this.regionPrestigeCount + Game.total.getVictories() * Settings.balance.tickBonusPerVictory;
    for (i = 0; i < prestigeBonuses; i++) this.calculateTickTimeForPrestige()
};
Tick.prototype.calculateTickTimeForPrestige = function () {
    this.millisPerTick = Math.max(Settings.tick.minimumMillisPerTick, this.millisPerTick * Settings.tick.prestigeFactor)
};
Tick.prototype.onRegionPrestigeCompletion = function () {
    this.regionPrestigeCount++;
    this.calculateTickTimeForPrestige()
};
Tick.prototype.getRegionPrestigeCount = function () {
    return this.regionPrestigeCount
};
Tick.prototype.setRegionPrestigeCount = function (regionPrestigeCount) {
    this.regionPrestigeCount = regionPrestigeCount
};
Tick.prototype.getMillisPerTick = function () {
    return this.millisPerTick
};
Tick.prototype.getLastTickTime = function () {
    return this.lastTickTime
};
Tick.prototype.setLastTickTime = function (lastTickTime) {
    this.lastTickTime = lastTickTime;
    if (this.lastTickTime > Time.getCurrentTime()) this.lastTickTime = Time.getCurrentTime()
};
Tick.prototype.getElapsedTicks = function () {
    var now = Time.getCurrentTime(),
        elapsedMillis = Math.max(0, now - this.lastTickTime);
    var elapsedTicks = Cast.toInt(elapsedMillis / this.millisPerTick);
    if (elapsedTicks > 0) this.lastTickTime = now;
    return elapsedTicks
};
Tick.prototype.convertToValuePerSecond = function (valuePerTick) {
    var secondsPerTick = this.millisPerTick / 1E3;
    return valuePerTick / secondsPerTick
};

function Total() {
    this.money = Settings.total.initialMoney;
    this.gameStartTime = Time.getCurrentTime();
    this.countToPurchase = CountEnum.PurchaseMax;
    this.cellsPurchased = 0;
    this.regionsFilled = 0;
    this.victories = 0;
    this.gameEndTime = 0
}
Total.prototype = new BaseModel;
Total.prototype.onSaveLoad = function () {};
Total.prototype.resetState = function () {
    this.money = Settings.total.initialMoney;
    this.gameStartTime = Time.getCurrentTime();
    this.countToPurchase = CountEnum.PurchaseMax;
    this.cellsPurchased = 0;
    this.regionsFilled = 0;
    this.gameEndTime = 0
};
Total.prototype.getMoney = function () {
    return this.money
};
Total.prototype.setMoney = function (money) {
    this.money = money
};
Total.prototype.decrementMoney = function (money) {
    this.money -= money;
    if (this.money < 0) this.money = 0
};
Total.prototype.getMoneyPerSecond = function () {
    return Game.tick.convertToValuePerSecond(this.getMoneyPerTick())
};
Total.prototype.getMoneyPerTick = function () {
    return Game.grid.getMoneyPerTick() * Game.grid.getBonusMultiplier()
};
Total.prototype.updateForFrame = function (elapsedTicks) {
    if (elapsedTicks > 0) this.money += this.getMoneyPerTick() * elapsedTicks
};
Total.prototype.getGameStartTime = function () {
    return this.gameStartTime
};
Total.prototype.setGameStartTime = function (gameStartTime) {
    this.gameStartTime = gameStartTime
};
Total.prototype.getPlayedTime = function () {
    return Time.getCurrentTime() - this.gameStartTime
};
Total.prototype.getPurchaseCountEnum = function () {
    return this.countToPurchase
};
Total.prototype.setPurchaseCountEnum = function (count) {
    this.countToPurchase = count
};
Total.prototype.getCellsPurchased = function () {
    return this.cellsPurchased
};
Total.prototype.setCellsPurchased = function (cellsPurchased) {
    this.cellsPurchased = cellsPurchased
};
Total.prototype.incrementCellsPurchased = function (cellsPurchased) {
    this.cellsPurchased += cellsPurchased
};
Total.prototype.getRegionsFilled = function () {
    return this.regionsFilled
};
Total.prototype.setRegionsFilled = function (regionsFilled) {
    this.regionsFilled = regionsFilled
};
Total.prototype.incrementRegionsFilled = function () {
    this.regionsFilled++
};
Total.prototype.getVictories = function () {
    return this.victories
};
Total.prototype.setVictories = function (victories) {
    this.victories = victories
};
Total.prototype.onGameVictory = function () {
    this.victories++;
    this.gameEndTime = Time.getCurrentTime()
};
Total.prototype.getGameEndTime = function () {
    return this.gameEndTime
};
Total.prototype.setGameEndTime = function (gameEndTime) {
    this.gameEndTime = gameEndTime
};

function BaseButton() {
    this.x = 0;
    this.y = 0;
    this.width = 0;
    this.height = 0;
    this.textX = 0;
    this.textY = 0;
    this.scale = 1;
    this.text = null
}
BaseButton.prototype.initializeButton = function (x, y, width, height, text, scale) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.textX = this.x + 5;
    this.textY = this.y + height / 2 + 5;
    this.scale = scale;
    this.text = text
};
BaseButton.prototype.getHeight = function () {
    return this.height
};
BaseButton.prototype.buttonContainsInternal = function (location, cellOrRegionOrigin) {
    var x0 = cellOrRegionOrigin.x + this.x * this.scale,
        y0 = cellOrRegionOrigin.y + this.y * this.scale,
        w = this.width * this.scale,
        h = this.height * this.scale;
    return location.x >= x0 && location.x < x0 + w && (location.y >= y0 && location.y < y0 + h)
};
BaseButton.prototype.renderButtonInternal = function (context, origin, hovered, enabled) {
    var invScale = 1 / this.scale,
        cellX = origin.x * invScale,
        cellY = origin.y * invScale;
    if (enabled)
        if (hovered) {
            context.fillStyle = "#AAAAAA";
            context.strokeStyle = "#FFFFFF"
        } else {
            context.fillStyle = "#888888";
            context.strokeStyle = "#FFFFFF"
        } else {
        context.fillStyle = "#666666";
        context.strokeStyle = "#AAAAAA"
    }
    var y = this.y,
        textY = this.textY;
    if (enabled && (hovered && Game.userInput.isLeftMouseDown())) {
        y += 1;
        textY += 1
    }
    context.fillRect(cellX + this.x, cellY +
        y, this.width, this.height);
    context.strokeRect(cellX + this.x, cellY + y, this.width, this.height);
    if (enabled) context.fillStyle = "#222222";
    else context.fillStyle = "#CCCCCC";
    context.fillText(this.text, cellX + this.textX, cellY + textY)
};

function ZoneButton() {}
ZoneButton.prototype = new BaseButton;
ZoneButton.prototype.buttonContains = function (location, zone) {
    return this.buttonContainsInternal(location, zone.getGridOrigin())
};

function DisableAutoPrestigeButton(x, y, scale) {
    this.initializeButton(x, y, 150, 40, "禁用自动声望", scale)
}
DisableAutoPrestigeButton.prototype = new ZoneButton;
DisableAutoPrestigeButton.prototype.renderButton = function (context, zone) {
    var origin = zone.getGridOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), zone),
        enabled = zone.isZoneUnlocked() && zone.isAutoPrestigeEnabled();
    this.renderButtonInternal(context, origin, hovered, enabled)
};

function EnableAutoPrestigeButton(x, y, scale) {
    this.initializeButton(x, y, 150, 40, "启用自动声望", scale)
}
EnableAutoPrestigeButton.prototype = new ZoneButton;
EnableAutoPrestigeButton.prototype.renderButton = function (context, zone) {
    var origin = zone.getGridOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), zone),
        enabled = zone.isZoneUnlocked() && !zone.isAutoPrestigeEnabled();
    this.renderButtonInternal(context, origin, hovered, enabled)
};

function ZoneView(renderScale, leftX, topY, yStep) {
    this.renderScale = renderScale;
    this.leftX = leftX;
    this.topY = topY;
    this.yStep = yStep;
    var invScale = 1 / this.renderScale;
    this.enableAutoPrestigeButton = new EnableAutoPrestigeButton(leftX * invScale, topY * invScale, this.renderScale);
    this.disableAutoPrestigeButton = new DisableAutoPrestigeButton(leftX * invScale, topY * invScale, this.renderScale)
}
ZoneView.prototype.handleClick = function (mousePosition, zone) {
    if (!zone.isZoneUnlocked() || (zone.isVictoryZone() || zone.isPrestigeCompletion())) return;
    if (zone.isAutoPrestigeEnabled()) {
        if (this.disableAutoPrestigeButton.buttonContains(mousePosition, zone)) zone.disableAutoPrestige()
    } else if (this.enableAutoPrestigeButton.buttonContains(mousePosition, zone)) zone.enableAutoPrestige()
};
ZoneView.prototype.renderView = function (context, zone) {
    if (!zone.isZoneUnlocked()) return;
    context.save();
    context.scale(this.renderScale, this.renderScale);
    var origin = zone.getGridOrigin(),
        bonusMultiplier = Game.grid.getBonusMultiplier(),
        moneyPerTick = zone.getMoneyPerTick();
    var invScale = 1 / this.renderScale;
    var xPos = (origin.x + this.leftX) * invScale,
        topY = (origin.y + this.topY) * invScale,
        buttonHeight = this.disableAutoPrestigeButton.getHeight(),
        yStep = this.yStep * invScale;
    var y = topY + yStep;
    if (!zone.isPrestigeCompletion() &&
        !zone.isVictoryZone()) {
        if (zone.isAutoPrestigeEnabled()) this.disableAutoPrestigeButton.renderButton(context, zone);
        else this.enableAutoPrestigeButton.renderButton(context, zone);
        y += buttonHeight
    }
    context.fillStyle = "#FFFFFF";
    context.fillText("金钱 / 秒: $" + Formatter.format(Game.tick.convertToValuePerSecond(moneyPerTick) * bonusMultiplier), xPos, y);
    y += yStep;
    context.fillText("已填充: " + zone.getFilledRegionPercent() + "%", xPos, y);
    context.restore()
};

function ZoneRepresentation() {
    this.zoneView1 = new ZoneView(65, 600, 600, 1100);
    this.zoneView2 = new ZoneView(75, 700, 700, 1250);
    this.zoneView3 = new ZoneView(85, 800, 800, 1400)
}
ZoneRepresentation.prototype.handleClick = function (mousePosition, zone) {
    var view = this.getViewForScale();
    if (view) view.handleClick(mousePosition, zone)
};
ZoneRepresentation.prototype.representZone = function (context, zone) {
    var view = this.getViewForScale();
    if (view) view.renderView(context, zone)
};
ZoneRepresentation.prototype.isZoomedOutPastRegions = function () {
    var zoom = Game.projection.getZoom();
    return zoom < 0.016
};
ZoneRepresentation.prototype.getViewForScale = function () {
    var zoom = Game.projection.getZoom();
    if (zoom >= 0.0125) return this.zoneView1;
    else if (zoom > 0.012) return this.zoneView2;
    else if (zoom > 0.01) return this.zoneView3;
    else return null
};

function RegionButton() {}
RegionButton.prototype = new BaseButton;
RegionButton.prototype.buttonContains = function (location, region) {
    return this.buttonContainsInternal(location, region.getRegionOrigin())
};

function PrestigeButton(x, y, scale) {
    this.initializeButton(x, y, 110, 40, "声望区域", scale)
}
PrestigeButton.prototype = new RegionButton;
PrestigeButton.prototype.renderButton = function (context, region) {
    var origin = region.getRegionOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), region);
    this.renderButtonInternal(context, origin, hovered, region.isPrestigeAvailable())
};

function UnlockButton(x, y, scale) {
    this.initializeButton(x, y, 100, 40, "解锁区域", scale)
}
UnlockButton.prototype = new RegionButton;
UnlockButton.prototype.renderButton = function (context, region) {
    var origin = region.getRegionOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), region),
        enabled = !region.isRegionUnlocked();
    this.renderButtonInternal(context, origin, hovered, enabled)
};

function EnableAutoPurchaseButton(x, y, scale) {
    this.initializeButton(x, y, 150, 40, "启用自动声望", scale)
}
EnableAutoPurchaseButton.prototype = new RegionButton;
EnableAutoPurchaseButton.prototype.renderButton = function (context, region) {
    var origin = region.getRegionOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), region),
        enabled = region.isRegionActive() && !region.isAutoPurchaseEnabled();
    this.renderButtonInternal(context, origin, hovered, enabled)
};

function DisableAutoPurchaseButton(x, y, scale) {
    this.initializeButton(x, y, 150, 40, "禁用自动声望", scale)
}
DisableAutoPurchaseButton.prototype = new RegionButton;
DisableAutoPurchaseButton.prototype.renderButton = function (context, region) {
    var origin = region.getRegionOrigin(),
        hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), region),
        enabled = region.isRegionActive() && region.isAutoPurchaseEnabled();
    this.renderButtonInternal(context, origin, hovered, enabled)
};

function RegionView(renderScale, leftX, topY, yStep, minimalMode) {
    this.renderScale = renderScale;
    this.leftX = leftX;
    this.topY = topY;
    this.yStep = yStep;
    this.minimalMode = minimalMode;
    var invScale = 1 / this.renderScale;
    this.prestigeButton = new PrestigeButton(leftX * invScale, topY * invScale, this.renderScale);
    this.enableAutoPurchaseButton = new EnableAutoPurchaseButton(leftX * invScale, topY * invScale, this.renderScale);
    this.disableAutoPurchaseButton = new DisableAutoPurchaseButton(leftX * invScale, topY * invScale, this.renderScale)
}
RegionView.prototype.handleClick = function (mousePosition, region) {
    if (!region.isRegionUnlocked()) return;
    if (this.minimalMode) return;
    if (region.isRegionFilled()) {
        if (region.isPrestigeAvailable() && this.prestigeButton.buttonContains(mousePosition, region)) region.prestigeRegion()
    } else if (region.isRegionActive())
        if (region.isAutoPurchaseEnabled()) {
            if (this.disableAutoPurchaseButton.buttonContains(mousePosition, region)) region.disableAutoPurchase()
        } else if (this.enableAutoPurchaseButton.buttonContains(mousePosition,
            region)) region.enableAutoPurchase()
};
RegionView.prototype.renderView = function (context, region) {
    if (!region.isRegionUnlocked()) return;
    context.save();
    context.scale(this.renderScale, this.renderScale);
    var origin = region.getRegionOrigin(),
        bonusMultiplier = Game.grid.getBonusMultiplier();
    var invScale = 1 / this.renderScale;
    var xPos = (origin.x + this.leftX) * invScale,
        topY = (origin.y + this.topY) * invScale,
        yStep = this.yStep * invScale,
        y = topY + yStep;
    if (this.minimalMode) {
        if (region.isRegionActive()) {
            context.fillStyle = "#FFFFFF";
            context.fillText(region.getPercentFilled() +
                "%", xPos, y)
        }
    } else {
        var buttonHeight = this.prestigeButton.getHeight();
        if (region.isRegionFilled() && region.isPrestigeAvailable()) {
            this.prestigeButton.renderButton(context, region);
            y += buttonHeight;
            context.fillStyle = "#FFFFFF";
            context.fillText("Prestige for tick rate bonus.", xPos, y);
            y += yStep
        } else if (region.isRegionActive()) {
            if (region.isAutoPurchaseEnabled()) this.disableAutoPurchaseButton.renderButton(context, region);
            else this.enableAutoPurchaseButton.renderButton(context, region);
            y += buttonHeight
        }
        context.fillStyle =
            "#FFFFFF";
        if (region.isColumnBonusRegion()) {
            if (region.isColumnBonusActive()) context.fillText("Full Column Bonus: x" + Settings.balance.columnBonusDisplay, xPos, y);
            else context.fillText("Full Column Bonus: 0", xPos, y);
            y += yStep
        } else if (region.isRowBonusRegion()) {
            if (region.isRowBonusActive()) context.fillText("Full Row Bonus: x" + Settings.balance.rowBonusDisplay, xPos, y);
            else context.fillText("Full Row Bonus: 0", xPos, y);
            y += yStep
        }
        context.fillText("金钱 / 秒: $" + Formatter.format(region.getMoneyPerSecond() *
            bonusMultiplier), xPos, y);
        if (region.isRegionActive()) {
            y += yStep;
            context.fillText("已填充: " + region.getPercentFilled() + "%", xPos, y)
        }
    }
    context.restore()
};

function RegionRepresentation() {
    this.regionView1 = new RegionView(6, 40, 40, 95, false);
    this.regionView2 = new RegionView(8, 60, 60, 120, false);
    this.regionView3 = new RegionView(11, 80, 80, 180, false);
    this.regionView4 = new RegionView(13, 100, 100, 220, false);
    this.regionView5 = new RegionView(20, 130, 130, 260, true);
    this.regionView6 = new RegionView(25, 170, 170, 260, true);
    this.regionView7 = new RegionView(32, 210, 210, 340, true)
}
RegionRepresentation.prototype.handleClick = function (mousePosition, region) {
    var view = this.getViewForScale();
    if (view) view.handleClick(mousePosition, region)
};
RegionRepresentation.prototype.representRegion = function (context, region) {
    var view = this.getViewForScale();
    if (view) view.renderView(context, region)
};
RegionRepresentation.prototype.isZoomedOutPastCells = function () {
    var zoom = Game.projection.getZoom();
    return zoom < 0.2
};
RegionRepresentation.prototype.getViewForScale = function () {
    var zoom = Game.projection.getZoom();
    if (zoom >= 0.15) return this.regionView1;
    else if (zoom > 0.11) return this.regionView2;
    else if (zoom > 0.08) return this.regionView3;
    else if (zoom > 0.06) return this.regionView4;
    else if (zoom > 0.04) return this.regionView5;
    else if (zoom > 0.035) return this.regionView6;
    else if (zoom > 0.025) return this.regionView7;
    else return null
};

function CellButton() {}
CellButton.prototype = new BaseButton;
CellButton.prototype.buttonContains = function (location, cell) {
    return this.buttonContainsInternal(location, cell.getOrigin())
};

function PurchaseCellButton(x, y, scale) {
    this.initializeButton(x, y, 60, 40, "购买格子", scale)
}
PurchaseCellButton.prototype = new CellButton;
PurchaseCellButton.prototype.renderButton = function (context, cell) {
    var hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), cell);
    this.renderButtonInternal(context, cell.getOrigin(), hovered, cell.isPurchasable() && !cell.isMaxCountReached())
};

function WinButton(x, y, width, height, scale) {
    this.initializeButton(x, y, width, height, "胜利!", scale)
}
WinButton.prototype = new CellButton;
WinButton.prototype.renderButton = function (context, cell) {
    var hovered = this.buttonContains(Game.worldMouse.getWorldPosition(), cell);
    this.renderButtonInternal(context, cell.getOrigin(), hovered, true)
};

function CellView() {}
CellView.prototype.handleClick = function (mousePosition, cell) {};
CellView.prototype.renderView = function (context, cell) {};

function CellViewHigh() {
    this.buyButton = new PurchaseCellButton(10, 10, 1);
    this.winButton = new WinButton(10, 10, 60, 40, 1)
}
CellViewHigh.prototype.handleClick = function (mousePosition, cell) {
    if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) {
        if (this.winButton.buttonContains(mousePosition, cell)) Game.onGameCompletion()
    } else if (cell.isPurchasable() && this.buyButton.buttonContains(mousePosition, cell)) cell.purchaseCell()
};
CellViewHigh.prototype.renderView = function (context, cell) {
    var origin = cell.getOrigin(),
        bonusMultiplier = Game.grid.getBonusMultiplier();
    if (cell.getRegion().isRegionFilled())
        if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) this.winButton.renderButton(context, cell);
        else {
            context.fillStyle = "#FFFFFF";
            context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), origin.x + 10, origin.y + 20);
            context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() *
                bonusMultiplier), origin.x + 10, origin.y + 40)
        } else if (cell.isMaxCountReached()) {
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), origin.x + 10, origin.y + 20);
        context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() * bonusMultiplier), origin.x + 10, origin.y + 40)
    } else {
        this.buyButton.renderButton(context, cell);
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()),
            origin.x + 10, origin.y + 70);
        context.fillText("成本: $" + Formatter.format(cell.getCost()), origin.x + 10, origin.y + 90);
        context.fillText("购买: " + Formatter.format(cell.getPurchaseCountNumber()), origin.x + 10, origin.y + 110);
        context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() * bonusMultiplier), origin.x + 10, origin.y + 130)
    }
};

function CellViewMedium() {
    this.renderScale = 1.5;
    var invScale = 1 / this.renderScale;
    this.buyButton = new PurchaseCellButton(10 * invScale, 10 * invScale, this.renderScale);
    this.winButton = new WinButton(10 * invScale, 10 * invScale, 60, 40, this.renderScale)
}
CellViewMedium.prototype.handleClick = function (mousePosition, cell) {
    if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) {
        if (this.winButton.buttonContains(mousePosition, cell)) Game.onGameCompletion()
    } else if (cell.isPurchasable() && this.buyButton.buttonContains(mousePosition, cell)) cell.purchaseCell()
};
CellViewMedium.prototype.renderView = function (context, cell) {
    context.save();
    context.scale(this.renderScale, this.renderScale);
    var origin = cell.getOrigin(),
        bonusMultiplier = Game.grid.getBonusMultiplier();
    var xPosition = this.getRenderPosition(origin.x + 10);
    if (cell.getRegion().isRegionFilled())
        if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) this.winButton.renderButton(context, cell);
        else {
            context.fillStyle = "#FFFFFF";
            context.fillText("数量: " + Formatter.format(cell.getCount()) +
                " / " + Formatter.format(cell.getMaxCount()), xPosition, this.getRenderPosition(origin.y + 30));
            context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() * bonusMultiplier), xPosition, this.getRenderPosition(origin.y + 60))
        } else if (cell.isMaxCountReached()) {
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPosition, this.getRenderPosition(origin.y + 30));
        context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() *
            bonusMultiplier), xPosition, this.getRenderPosition(origin.y + 60))
    } else {
        this.buyButton.renderButton(context, cell);
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPosition, this.getRenderPosition(origin.y + 100));
        context.fillText("成本: $" + Formatter.format(cell.getCost()), xPosition, this.getRenderPosition(origin.y + 130));
        context.fillText("购买: " + Formatter.format(cell.getPurchaseCountNumber()), xPosition, this.getRenderPosition(origin.y +
            160));
        context.fillText("金钱/秒: " + Formatter.format(cell.getMoneyPerSecond() * bonusMultiplier), xPosition, this.getRenderPosition(origin.y + 190))
    }
    context.restore()
};
CellViewMedium.prototype.getRenderPosition = function (coordinate) {
    return coordinate / this.renderScale
};

function CellViewLow() {
    this.renderScale = 2;
    var invScale = 1 / this.renderScale;
    this.buyButton = new PurchaseCellButton(15 * invScale, 15 * invScale, this.renderScale);
    this.winButton = new WinButton(15 * invScale, 15 * invScale, 60, 40, this.renderScale)
}
CellViewLow.prototype.handleClick = function (mousePosition, cell) {
    if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) {
        if (this.winButton.buttonContains(mousePosition, cell)) Game.onGameCompletion()
    } else if (cell.isPurchasable() && this.buyButton.buttonContains(mousePosition, cell)) cell.purchaseCell()
};
CellViewLow.prototype.renderView = function (context, cell) {
    context.save();
    context.scale(this.renderScale, this.renderScale);
    var origin = cell.getOrigin();
    var invScale = 1 / this.renderScale;
    var xPos = (origin.x + 15) * invScale;
    if (cell.getRegion().isRegionFilled())
        if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) this.winButton.renderButton(context, cell);
        else {
            context.fillStyle = "#FFFFFF";
            context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()),
                xPos, (origin.y + 45) * invScale)
        } else if (cell.isMaxCountReached()) {
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPos, (origin.y + 45) * invScale)
    } else {
        this.buyButton.renderButton(context, cell);
        context.fillStyle = "#FFFFFF";
        context.fillText("数量: " + Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPos, (origin.y + 125) * invScale);
        context.fillText("成本: $" + Formatter.format(cell.getCost()), xPos, (origin.y +
            160) * invScale);
        context.fillText("购买: " + Formatter.format(cell.getPurchaseCountNumber()), xPos, (origin.y + 195) * invScale)
    }
    context.restore()
};

function CellViewLower() {
    this.renderScale = 3;
    var invScale = 1 / this.renderScale;
    this.buyButton = new PurchaseCellButton(15 * invScale, 15 * invScale, this.renderScale);
    this.winButton = new WinButton(15 * invScale, 15 * invScale, 60, 40, this.renderScale)
}
CellViewLower.prototype.handleClick = function (mousePosition, cell) {
    if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) {
        if (this.winButton.buttonContains(mousePosition, cell)) Game.onGameCompletion()
    } else if (cell.isPurchasable() && this.buyButton.buttonContains(mousePosition, cell)) cell.purchaseCell()
};
CellViewLower.prototype.renderView = function (context, cell) {
    context.save();
    context.scale(this.renderScale, this.renderScale);
    var origin = cell.getOrigin();
    var invScale = 1 / this.renderScale;
    var xPos = (origin.x + 15) * invScale;
    if (cell.getRegion().isRegionFilled())
        if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) this.winButton.renderButton(context, cell);
        else {
            context.fillStyle = "#FFFFFF";
            context.fillText(Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()),
                xPos, (origin.y + 60) * invScale)
        } else if (cell.isMaxCountReached()) {
        context.fillStyle = "#FFFFFF";
        context.fillText(Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPos, (origin.y + 60) * invScale)
    } else {
        this.buyButton.renderButton(context, cell);
        context.fillStyle = "#FFFFFF";
        context.fillText(Formatter.format(cell.getCount()) + " / " + Formatter.format(cell.getMaxCount()), xPos, (origin.y + 180) * invScale);
        context.fillText("$" + Formatter.format(cell.getCost()), xPos, (origin.y + 230) * invScale)
    }
    context.restore()
};

function CellViewLowest() {
    this.renderScale = 3.6;
    var invScale = 1 / this.renderScale;
    this.buyButton = new PurchaseCellButton(15 * invScale, 15 * invScale, this.renderScale);
    this.winButton = new WinButton(15 * invScale, 15 * invScale, 60, 40, this.renderScale)
}
CellViewLowest.prototype.handleClick = function (mousePosition, cell) {
    if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) {
        if (this.winButton.buttonContains(mousePosition, cell)) Game.onGameCompletion()
    } else if (cell.isPurchasable() && this.buyButton.buttonContains(mousePosition, cell)) cell.purchaseCell()
};
CellViewLowest.prototype.renderView = function (context, cell) {
    context.save();
    context.scale(this.renderScale, this.renderScale);
    if (cell.getRegion().isRegionFilled()) {
        if (Game.grid.isGridFilled() && (cell.getRegion().isVictoryRegion() && cell.isLastCell())) this.winButton.renderButton(context, cell)
    } else if (!cell.isMaxCountReached()) {
        var origin = cell.getOrigin(),
            invScale = 1 / this.renderScale,
            xPos = (origin.x + 20) * invScale;
        this.buyButton.renderButton(context, cell);
        context.fillStyle = "#FFFFFF";
        context.fillText(Formatter.format(cell.getCount()) +
            " / " + Formatter.format(cell.getMaxCount()), xPos, (origin.y + 220) * invScale)
    }
    context.restore()
};

function CellRepresentation() {
    this.highDetailView = new CellViewHigh;
    this.mediumDetailView = new CellViewMedium;
    this.lowDetailView = new CellViewLow;
    this.lowerDetailView = new CellViewLower;
    this.lowestDetailView = new CellViewLowest
}
CellRepresentation.prototype.handleClick = function (mousePosition, cell) {
    var view = this.getViewForScale();
    if (view) view.handleClick(mousePosition, cell)
};
CellRepresentation.prototype.representCell = function (context, cell) {
    var view = this.getViewForScale();
    if (view) view.renderView(context, cell)
};
CellRepresentation.prototype.getViewForScale = function () {
    var zoom = Game.projection.getZoom();
    if (zoom > 0.75) return this.highDetailView;
    else if (zoom > 0.55) return this.mediumDetailView;
    else if (zoom > 0.4) return this.lowDetailView;
    else if (zoom > 0.25) return this.lowerDetailView;
    else if (zoom > 0.1) return this.lowestDetailView;
    else return null
};

function Building() {
    this.initialCost = 0;
    this.costFactor = 0;
    this.logCostFactor = -1;
    this.counts = [];
    this.countIndex = 0;
    this.maxCount = Settings.cell.maxCountBase;
    this.cost = 0;
    this.maxAffordableCount = 0;
    this.moneyPerTickFactor = 0;
    this.baseMoneyPerTick = 0;
    this.moneyPerTick = 0
}
Building.prototype.isMaxCountReached = function () {
    return this.getCount() >= this.maxCount
};
Building.prototype.getMaxCount = function () {
    return this.maxCount
};
Building.prototype.setMaxCount = function (maxCount) {
    this.maxCount = maxCount
};
Building.prototype.getCount = function () {
    return this.counts[this.countIndex]
};
Building.prototype.setCountData = function (counts, countIndex, autoPurchaseMode) {
    this.counts = counts;
    this.countIndex = countIndex;
    this.moneyPerTick = this.calculateMoneyPerTick();
    this.calculateForCountChange(autoPurchaseMode)
};
Building.prototype.setCount = function (count) {
    this.counts[this.countIndex] = Math.min(this.maxCount, count);
    this.calculateForCountChange(false)
};
Building.prototype.setInitialCost = function (initialCost) {
    if (initialCost < 0) console.log("initial cost: " + initialCost);
    this.initialCost = initialCost
};
Building.prototype.setCostFactor = function (costFactor) {
    this.costFactor = costFactor;
    this.logCostFactor = -1
};
Building.prototype.isPurchasable = function () {
    return this.cost <= Game.total.getMoney()
};
Building.prototype.getCost = function () {
    return this.cost
};
Building.prototype.getCostForCount = function (purchaseCountEnum) {
    var purchaseCount = this.getPurchaseCount(purchaseCountEnum);
    if (purchaseCount == 0) return 0;
    var singlePurchaseCost = this.calculateSinglePurchaseCost();
    if (purchaseCount == 1) return singlePurchaseCost;
    else return this.calculateBulkPurchaseCost(singlePurchaseCost, purchaseCount)
};
Building.prototype.getCostForActualCount = function (purchaseCount) {
    if (purchaseCount <= 0) return 0;
    var singlePurchaseCost = this.calculateSinglePurchaseCost();
    if (purchaseCount == 1) return singlePurchaseCost;
    else return this.calculateBulkPurchaseCost(singlePurchaseCost, purchaseCount)
};
Building.prototype.getCostForTotalCount = function (count) {
    if (count <= 0) return 0;
    var firstPurchaseCost = this.calculateAbsolutePurchaseCost(1);
    if (count == 1) return firstPurchaseCost;
    else return this.calculateBulkPurchaseCost(firstPurchaseCost, count)
};
Building.prototype.getPurchaseCountNumber = function () {
    return this.getPurchaseCount(Game.total.getPurchaseCountEnum())
};
Building.prototype.getPurchaseCount = function (countEnum) {
    switch (countEnum) {
        case CountEnum.Purchase1:
            return 1;
        case CountEnum.PurchaseMax:
            return this.maxAffordableCount;
        default:
            console.log("error: bad count enum value: " + countEnum);
            return 0
    }
};
Building.prototype.setMoneyPerTickFactor = function (rateFactor) {
    this.moneyPerTickFactor = rateFactor
};
Building.prototype.setBaseMoneyPerTick = function (baseRate) {
    this.baseMoneyPerTick = baseRate
};
Building.prototype.getMoneyPerTick = function () {
    return this.moneyPerTick
};
Building.prototype.getMoneyPerSecond = function () {
    return Game.tick.convertToValuePerSecond(this.moneyPerTick)
};
Building.prototype.purchaseAmount = function (countToPurchase) {
    var currentCount = this.getCount();
    if (currentCount >= this.maxCount) return 0;
    var costForCount = this.getCostForCount(countToPurchase);
    if (costForCount == 0) return 0;
    if (costForCount > Game.total.getMoney()) return 0;
    var purchasedCount = this.getPurchaseCount(countToPurchase);
    if (purchasedCount === 0) return 0;
    Game.total.decrementMoney(costForCount);
    this.setCount(currentCount + purchasedCount);
    this.moneyPerTick = this.calculateMoneyPerTick();
    return purchasedCount
};
Building.prototype.purchaseActualAmount = function (count) {
    var currentCount = this.getCount();
    if (currentCount >= this.maxCount) return 0;
    if (count + currentCount > this.maxCount) count = this.maxCount - currentCount;
    var costForCount = this.getCostForActualCount(count);
    if (costForCount == 0) return 0;
    if (costForCount < 0) {
        console.log("BUG: cost for count is negative: " + costForCount + " count: " + count);
        return 0
    }
    if (costForCount > Game.total.getMoney()) return 0;
    Game.total.decrementMoney(costForCount);
    this.setCount(currentCount + count);
    this.moneyPerTick = this.calculateMoneyPerTick();
    return count
};
Building.prototype.calculateForCountChange = function (autoPurchaseMode) {
    if (autoPurchaseMode) {
        this.cost = this.calculateSinglePurchaseCost();
        return
    }
    if (Game.total.getPurchaseCountEnum() == CountEnum.PurchaseMax) this.updateMaxPurchase();
    else this.cost = this.calculateSinglePurchaseCost()
};
Building.prototype.updateMaxPurchase = function () {
    var money = Game.total.getMoney();
    var singlePurchaseCost = this.calculateSinglePurchaseCost();
    this.maxAffordableCount = this.calculateMaxAffordable(money, singlePurchaseCost);
    if (this.maxAffordableCount > 0) {
        var count = this.getCount();
        if (count + this.maxAffordableCount > this.maxCount) {
            this.maxAffordableCount = Math.max(0, this.maxCount - count);
            if (this.maxAffordableCount == 0) {
                this.cost = singlePurchaseCost;
                return
            }
        }
        this.cost = this.calculateBulkPurchaseCost(singlePurchaseCost,
            this.maxAffordableCount)
    } else this.cost = singlePurchaseCost
};
Building.prototype.calculateMoneyPerTick = function () {
    return this.calculateRateForCount(this.getCount())
};
Building.prototype.calculateRateForCount = function (count) {
    if (count < 1) return 0;
    else return this.baseMoneyPerTick * Math.pow(this.moneyPerTickFactor, count)
};
Building.prototype.calculateSinglePurchaseCost = function () {
    return this.initialCost * Math.pow(this.costFactor, this.getCount() + 1)
};
Building.prototype.calculateAbsolutePurchaseCost = function (count) {
    return this.initialCost * Math.pow(this.costFactor, count)
};
Building.prototype.calculateBulkPurchaseCost = function (singlePurchaseCost, numberToPurchase) {
    return singlePurchaseCost * ((Math.pow(this.costFactor, numberToPurchase) - 1) / (this.costFactor - 1))
};
Building.prototype.calculateMaxAffordable = function (money, singlePurchaseCost) {
    if (singlePurchaseCost > money) return 0;
    if (this.logCostFactor == -1) this.logCostFactor = Math.log(this.costFactor);
    return Cast.toInt(Math.log(1 + (this.costFactor - 1) * money / singlePurchaseCost) / this.logCostFactor)
};

function GridCell(cellIndex) {
    this.origin = new Vector(0, 0);
    this.gridRegion = null;
    this.cellUnlocked = false;
    this.cellIndex = cellIndex
}
GridCell.prototype = new Building;
GridCell.prototype.setCellState = function (region, worldCol, worldRow, cellIndex, setStateForAutoPurchase) {
    this.gridRegion = region;
    this.cellUnlocked = false;
    this.origin.set(worldCol * Settings.cell.width, worldRow * Settings.cell.height);
    this.initializeCostAndRate();
    this.setCountData(region.getCellCounts(), cellIndex, setStateForAutoPurchase)
};
GridCell.prototype.setCellStateInactive = function (region, worldCol, worldRow, cellCounts, cellIndex) {
    this.gridRegion = region;
    this.cellUnlocked = region.isRegionUnlocked();
    this.origin.set(worldCol * Settings.cell.width, worldRow * Settings.cell.height);
    this.initializeCostAndRate();
    this.setCountData(cellCounts, cellIndex, true)
};
GridCell.prototype.initializeCostAndRate = function () {
    var distancePenalty = this.gridRegion.getCellDistancePenalty(),
        distanceFromOrigin = Cast.toInt(this.origin.length() / Settings.balance.cellCostDistanceDivisor),
        baseCostAndRate = distanceFromOrigin + distancePenalty + 1;
    this.setMaxCount(this.gridRegion.getMaxCount());
    this.setInitialCost(baseCostAndRate);
    this.setCostFactor(this.gridRegion.getRegionCostFactor());
    this.setMoneyPerTickFactor(this.gridRegion.getRegionMoneyFactor());
    this.setBaseMoneyPerTick(baseCostAndRate)
};
GridCell.prototype.getRegion = function () {
    return this.gridRegion
};
GridCell.prototype.getOrigin = function () {
    return this.origin
};
GridCell.prototype.isCellUnlocked = function () {
    return this.cellUnlocked
};
GridCell.prototype.setCellUnlocked = function (cellUnlocked) {
    this.cellUnlocked = cellUnlocked
};
GridCell.prototype.cellContains = function (vector) {
    var x0 = this.origin.x,
        y0 = this.origin.y;
    return vector.x >= x0 && vector.x < x0 + Settings.cell.width && (vector.y >= y0 && vector.y < y0 + Settings.cell.height)
};
GridCell.prototype.isCellVisible = function (spaceCenter, visibleHalfWidth, visibleHalfHeight) {
    return this.isCellVisibleX(spaceCenter, visibleHalfWidth) && this.isCellVisibleY(spaceCenter, visibleHalfHeight)
};
GridCell.prototype.isCellVisibleX = function (gridCenter, visibleHalfWidth) {
    var minCellX = this.origin.x,
        maxCellX = minCellX + Settings.cell.width,
        minVisibleX = gridCenter.x - visibleHalfWidth,
        maxVisibleX = gridCenter.x + visibleHalfWidth;
    return minCellX < maxVisibleX && maxCellX >= minVisibleX
};
GridCell.prototype.isCellVisibleY = function (gridCenter, visibleHalfHeight) {
    var minCellY = this.origin.y,
        maxCellY = minCellY + Settings.cell.height,
        minVisibleY = gridCenter.y - visibleHalfHeight,
        maxVisibleY = gridCenter.y + visibleHalfHeight;
    return minCellY < maxVisibleY && maxCellY >= minVisibleY
};
GridCell.prototype.handleMouseClick = function (mousePosition) {
    if (!this.cellUnlocked) return;
    Game.cellRepresentation.handleClick(mousePosition, this)
};
GridCell.prototype.purchaseCell = function () {
    var purchasedAmount = this.purchaseAmount(Game.total.getPurchaseCountEnum());
    if (!purchasedAmount) return;
    this.gridRegion.onCellPurchase(purchasedAmount)
};
GridCell.prototype.purchaseCells = function (purchaseAmount) {
    var purchasedAmount = this.purchaseActualAmount(purchaseAmount);
    if (purchasedAmount == 0) return;
    this.gridRegion.onCellPurchase(purchasedAmount)
};
GridCell.prototype.renderCell = function (context) {
    if (!this.cellUnlocked) return;
    Game.cellRepresentation.representCell(context, this)
};
GridCell.prototype.isLastCell = function () {
    return this.cellIndex == Settings.region.numCellColsInRegion * Settings.region.numCellRowsInRegion - 1
};
var CELL_VECTOR = new Vector(0, 0);
GridCell.prototype.renderCellBackground = function (context) {
    var cellWidth = Settings.cell.width,
        cellHeight = Settings.cell.height;
    var origin = this.origin;
    Game.projection.worldToScreen(origin, CELL_VECTOR);
    var leftX = CELL_VECTOR.x + 2,
        topY = CELL_VECTOR.y + 2;
    CELL_VECTOR.copy(origin);
    CELL_VECTOR.addXY(cellWidth, cellHeight);
    Game.projection.worldToScreen(CELL_VECTOR, CELL_VECTOR);
    var rightX = CELL_VECTOR.x - 2,
        bottomY = CELL_VECTOR.y - 2;
    context.save();
    context.beginPath();
    context.moveTo(leftX, topY);
    context.lineTo(leftX, bottomY);
    context.lineTo(rightX, bottomY);
    context.lineTo(rightX, topY);
    context.closePath();
    context.restore();
    if (!this.cellUnlocked) context.fillStyle = "#777777";
    else if (this.gridRegion.isRegionActive())
        if (this.isMaxCountReached()) context.fillStyle = this.gridRegion.getZone().getColors().getFilledColor();
        else if (!this.isPurchasable()) context.fillStyle = "#222222";
    else context.fillStyle = this.gridRegion.getFillStyle();
    else if (this.gridRegion.isVictoryRegion() && (Game.grid.isGridFilled() && this.isLastCell())) context.fillStyle =
        Settings.colors.cellVictoryColor;
    else context.fillStyle = this.gridRegion.getFillStyle();
    context.fill()
};

function Colors(colorSettings) {
    this.lockedColor = colorSettings.locked;
    this.filledColor = colorSettings.filled;
    this.prestigeColor = colorSettings.prestige;
    this.activePrestigeColor = colorSettings.activePrestige;
    this.prestigeVeteranColor = colorSettings.prestigeVeteran;
    this.secondPrestigeVeteranColor = colorSettings.secondPrestigeVeteran;
    this.activeColor = colorSettings.active
}
Colors.prototype.getFilledColor = function () {
    return this.filledColor
};
Colors.prototype.getSecondPrestigeVeteranColor = function () {
    return this.secondPrestigeVeteranColor
};
Colors.prototype.getFillStyle = function (region) {
    if (!region.isRegionUnlocked()) return this.lockedColor;
    else if (region.isRegionActive())
        if (region.getPrestigeCount() > 0) return this.activePrestigeColor;
        else return this.activeColor;
    else if (region.isRegionFilled())
        if (region.isVictoryRegion() && Game.grid.isGridFilled()) return Settings.colors.victoryColor;
        else if (region.isPrestigeAvailable()) return this.prestigeColor;
    else if (region.getPrestigeCount() == 1) return this.prestigeVeteranColor;
    else if (region.getPrestigeCount() >
        1) return this.secondPrestigeVeteranColor;
    else return this.filledColor;
    else return this.activeColor
};

function BalanceSettings(zoneCol, zoneRow) {
    var settings = Settings.balance.settings;
    this.zoneCellDistancePenalty = zoneCol * settings.zoneCellDistancePenaltyPerIncrement;
    this.prestigeCellDistancePenalty = settings.prestigeCellDistancePenalty;
    this.zoneCostFactor = (zoneCol + zoneRow) * settings.zoneCostFactorPerIncrement;
    this.zoneMoneyFactor = (zoneCol + zoneRow) * settings.zoneMoneyFactorPerIncrement
}
BalanceSettings.prototype.getZoneCellDistancePenalty = function () {
    return this.zoneCellDistancePenalty
};
BalanceSettings.prototype.getPrestigeCellDistancePenalty = function () {
    return this.prestigeCellDistancePenalty
};
BalanceSettings.prototype.getZoneCostFactor = function () {
    return this.zoneCostFactor
};
BalanceSettings.prototype.getZoneMoneyFactor = function () {
    return this.zoneMoneyFactor
};
var BonusType = {
    NONE: 0,
    COLUMN_BONUS: 1,
    ROW_BONUS: 2,
    VICTORY: 3
};

function GridRegion(zone, regionCol, regionRow) {
    this.zone = zone;
    this.regionCol = regionCol;
    this.regionRow = regionRow;
    this.regionOrigin = new Vector(regionCol * Settings.region.numCellColsInRegion * Settings.cell.width, regionRow * Settings.region.numCellColsInRegion * Settings.cell.height);
    this.regionBonusType = BonusType.NONE;
    this.cellCounts = null;
    this.moneyPerTick = 0;
    this.regionUnlocked = false;
    this.regionFilled = false;
    this.autoPurchaseEnabled = false;
    this.minCellCount = 0;
    this.totalCellCounts = 0;
    this.prestigeCount = 0;
    this.prestigeAvailable =
        false;
    this.regionScore = -1
}
GridRegion.prototype = new BaseModel;
GridRegion.prototype.resetState = function () {
    this.moneyPerTick = 0;
    this.autoPurchaseEnabled = false;
    this.regionUnlocked = false;
    this.regionFilled = false;
    this.minCellCount = 0;
    this.totalCellCounts = 0;
    this.prestigeAvailable = false;
    this.prestigeCount = 0;
    this.cellCounts = null;
    this.regionScore = -1
};
GridRegion.prototype.onSaveLoad = function () {
    this.regionScore = -1;
    this.moneyPerTick = this.calculateMoneyPerTickOnSaveLoad();
    if (this.isRegionActive()) this.calculateTotalAndMinCellCounts()
};
GridRegion.prototype.calculateMoneyPerTickOnSaveLoad = function () {
    if (!this.regionUnlocked) return 0;
    else {
        Game.virtualRegion.setRegion(this, false);
        return Game.virtualRegion.calculateMoneyPerTick()
    }
};
GridRegion.prototype.getCellDistancePenalty = function () {
    var balanceSettings = this.zone.getBalanceSettings(),
        regionPenalty = this.regionCol * Settings.balance.cellCostPenaltyPerRegionIncrement,
        penalty = regionPenalty + balanceSettings.getZoneCellDistancePenalty();
    if (this.prestigeCount > 0) penalty += balanceSettings.getPrestigeCellDistancePenalty();
    return penalty
};
GridRegion.prototype.createRegionCells = function () {
    var col, row, numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion;
    this.cellCounts = [];
    for (col = 0; col < numCols; col++)
        for (row = 0; row < numRows; row++) this.cellCounts.push(0)
};
GridRegion.prototype.getZone = function () {
    return this.zone
};
GridRegion.prototype.getRegionCostFactor = function () {
    var cols = this.regionCol + this.prestigeCount,
        columnCostFactor = cols * Settings.balance.regionCostFactorPerCol,
        zoneCostFactor = this.zone.getBalanceSettings().getZoneCostFactor();
    return Settings.balance.baseRegionCostFactor + columnCostFactor + zoneCostFactor
};
GridRegion.prototype.getRegionMoneyFactor = function () {
    var cols = this.regionCol + this.prestigeCount,
        columnMoneyFactor = cols * Settings.balance.regionMoneyFactorPerCol,
        zoneMoneyFactor = this.zone.getBalanceSettings().getZoneMoneyFactor();
    return Settings.balance.baseRegionMoneyFactor + columnMoneyFactor + zoneMoneyFactor
};
GridRegion.prototype.isAutoPurchaseEnabled = function () {
    return this.autoPurchaseEnabled
};
GridRegion.prototype.setAutoPurchaseEnabled = function (autoPurchaseEnabled) {
    if (this.autoPurchaseEnabled != autoPurchaseEnabled) {
        this.autoPurchaseEnabled = autoPurchaseEnabled;
        if (autoPurchaseEnabled) Game.autoPurchaseSettings.incrementAutoPurchaseRegionCount();
        else Game.autoPurchaseSettings.decrementAutoPurchaseRegionCount()
    }
};
GridRegion.prototype.enableAutoPurchase = function () {
    if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.SINGLE) Game.autoPurchaseSettings.setAutoPurchasingInAllRegions(false);
    this.setAutoPurchaseEnabled(true)
};
GridRegion.prototype.disableAutoPurchase = function () {
    this.setAutoPurchaseEnabled(false)
};
GridRegion.prototype.getRegionOrigin = function () {
    return this.regionOrigin
};
GridRegion.prototype.getRegionCol = function () {
    return this.regionCol
};
GridRegion.prototype.getRegionRow = function () {
    return this.regionRow
};
GridRegion.prototype.getCellCounts = function () {
    return this.cellCounts
};
GridRegion.prototype.setCellCounts = function (cellCounts) {
    this.cellCounts = cellCounts
};
GridRegion.prototype.getPercentFilled = function () {
    if (!this.regionUnlocked) return 0;
    else if (this.regionFilled) return 100;
    else {
        var maxCount = this.getMaxCount();
        if (maxCount == 0) return 0;
        var numCells = Settings.region.numCellColsInRegion * Settings.region.numCellRowsInRegion,
            filledRatio = this.totalCellCounts / (maxCount * numCells);
        return Math.round(100 * filledRatio)
    }
};
GridRegion.prototype.regionContains = function (vector) {
    var x0 = this.regionOrigin.x,
        y0 = this.regionOrigin.y,
        regionWidth = Settings.region.numCellColsInRegion * Settings.cell.width,
        regionHeight = Settings.region.numCellRowsInRegion * Settings.cell.height;
    return vector.x >= x0 && vector.x < x0 + regionWidth && (vector.y >= y0 && vector.y < y0 + regionHeight)
};
GridRegion.prototype.isRegionVisible = function (spaceCenter, visibleHalfWidth, visibleHalfHeight) {
    return this.isRegionVisibleX(spaceCenter, visibleHalfWidth) && this.isRegionVisibleY(spaceCenter, visibleHalfHeight)
};
GridRegion.prototype.isRegionVisibleX = function (spaceCenter, visibleHalfWidth) {
    var minX = this.regionOrigin.x,
        maxX = minX + Settings.region.numCellColsInRegion * Settings.cell.width,
        minVisibleX = spaceCenter.x - visibleHalfWidth,
        maxVisibleX = spaceCenter.x + visibleHalfWidth;
    return minX < maxVisibleX && maxX >= minVisibleX
};
GridRegion.prototype.isRegionVisibleY = function (spaceCenter, visibleHalfHeight) {
    var minY = this.regionOrigin.y,
        maxY = minY + Settings.region.numCellRowsInRegion * Settings.cell.height,
        minVisibleY = spaceCenter.y - visibleHalfHeight,
        maxVisibleY = spaceCenter.y + visibleHalfHeight;
    return minY < maxVisibleY && maxY >= minVisibleY
};
GridRegion.prototype.getMoneyPerTick = function () {
    return this.moneyPerTick
};
GridRegion.prototype.getMoneyPerSecond = function () {
    return Game.tick.convertToValuePerSecond(this.moneyPerTick)
};
GridRegion.prototype.isRegionUnlocked = function () {
    return this.regionUnlocked
};
GridRegion.prototype.setRegionUnlocked = function (regionUnlocked) {
    this.regionUnlocked = regionUnlocked
};
GridRegion.prototype.unlockRegion = function () {
    if (this.regionUnlocked) return;
    this.regionUnlocked = true;
    this.createRegionCells();
    if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.AUTOMATIC) this.enableAutoPurchase()
};
GridRegion.prototype.prestigeRegion = function () {
    if (!this.prestigeAvailable) return;
    this.prestigeCount++;
    this.regionScore = -1;
    this.minCellCount = 0;
    this.totalCellCounts = 0;
    this.disableAutoPurchase();
    this.regionFilled = false;
    this.regionUnlocked = false;
    this.prestigeAvailable = false;
    this.unlockRegion();
    this.zone.onRegionPrestige(this);
    if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.PRESTIGE_FOCUS) this.enableAutoPurchase();
    else if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.COLUMN_FOCUS) Game.autoPurchaseSettings.setAutoPurchasingInFirstUnfilledColumn();
    else if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.ROW_FOCUS) Game.autoPurchaseSettings.setAutoPurchasingInFirstUnfilledRow()
};
GridRegion.prototype.isPrestigeAvailable = function () {
    return this.prestigeAvailable
};
GridRegion.prototype.setPrestigeAvailable = function (prestigeAvailable) {
    this.prestigeAvailable = prestigeAvailable
};
GridRegion.prototype.declarePrestigeAvailable = function () {
    this.prestigeAvailable = true;
    if (this.zone.isAutoPrestigeEnabled()) this.prestigeRegion()
};
GridRegion.prototype.getPrestigeCount = function () {
    return this.prestigeCount
};
GridRegion.prototype.setPrestigeCount = function (prestigeCount) {
    this.prestigeCount = prestigeCount
};
GridRegion.prototype.getMinCellCount = function () {
    return this.minCellCount
};
GridRegion.prototype.getMaxCount = function () {
    var rows = this.regionRow + this.prestigeCount,
        cols = this.regionCol,
        countPerRows = Settings.cell.maxCountPerRowIncrement * rows,
        countPerCols = Settings.cell.maxCountPerColIncrement * cols;
    return Cast.toInt(Settings.cell.maxCountBase + countPerRows + countPerCols)
};
GridRegion.prototype.getRegionScore = function () {
    return this.regionScore
};
GridRegion.prototype.setRegionScore = function (regionScore) {
    this.regionScore = regionScore
};
GridRegion.prototype.isRegionFilled = function () {
    return this.regionFilled
};
GridRegion.prototype.setRegionFilled = function (regionFilled) {
    this.regionFilled = regionFilled
};
GridRegion.prototype.isRegionActive = function () {
    return this.regionUnlocked && !this.regionFilled
};
GridRegion.prototype.onCellPurchase = function (purchasedCells) {
    if (!this.regionUnlocked || this.regionFilled) return;
    this.totalCellCounts += purchasedCells;
    if (!Game.virtualRegion.isRegionSet(this)) return;
    this.moneyPerTick = Game.virtualRegion.calculateMoneyPerTick();
    var maxCount = this.getMaxCount();
    if (this.minCellCount < maxCount) this.calculateTotalAndMinCellCounts();
    if (this.minCellCount >= maxCount) this.onRegionFilled();
    Game.total.incrementCellsPurchased(purchasedCells);
    this.zone.onCellPurchase()
};
GridRegion.prototype.onRegionFilled = function () {
    this.disableAutoPurchase();
    this.regionFilled = true;
    this.cellCounts.length = 0;
    this.cellCounts = null;
    if (this.prestigeCount > 0) {
        Game.tick.onRegionPrestigeCompletion();
        this.onPrestigeRegionFilled()
    }
    Game.total.incrementRegionsFilled();
    this.zone.onRegionFilled(this);
    if (Game.autoPurchaseSettings.getAutoPurchaseMode() == AutoPurchaseMode.COLUMN_FOCUS) Game.autoPurchaseSettings.setAutoPurchasingInFirstUnfilledColumn();
    else if (Game.autoPurchaseSettings.getAutoPurchaseMode() ==
        AutoPurchaseMode.ROW_FOCUS) Game.autoPurchaseSettings.setAutoPurchasingInFirstUnfilledRow()
};
GridRegion.prototype.onPrestigeRegionFilled = function () {
    var nextRegion = null;
    if (this.prestigeCount == 1) nextRegion = this.zone.getRegionAbsolute(this.regionCol, this.regionRow + 1);
    else if (this.prestigeCount == 2) nextRegion = this.zone.getRegionAbsolute(this.regionCol + 1, this.regionRow);
    else console.log("onPrestigeRegionFilled prestige count: " + this.prestigeCount);
    if (nextRegion) nextRegion.declarePrestigeAvailable()
};
GridRegion.prototype.calculateTotalAndMinCellCounts = function () {
    if (!this.regionUnlocked) {
        this.minCellCount = 0;
        this.totalCellCounts = 0;
        return
    }
    var maxCount = this.getMaxCount();
    if (this.regionFilled) {
        this.minCellCount = maxCount;
        this.totalCellCounts = maxCount * (Settings.region.numCellColsInRegion * Settings.region.numCellRowsInRegion);
        return
    }
    if (!this.cellCounts) return;
    var i, count, totalCount = 0,
        minCount = this.cellCounts[0];
    for (i = 1; i < this.cellCounts.length; i++) {
        count = this.cellCounts[i];
        totalCount += count;
        if (count < minCount) minCount =
            count
    }
    this.minCellCount = minCount;
    this.totalCellCounts = totalCount;
    var maxPossibleCount = maxCount * (Settings.region.numCellColsInRegion * Settings.region.numCellRowsInRegion);
    if (this.totalCellCounts > maxPossibleCount) this.totalCellCounts = maxPossibleCount;
    if (this.minCellCount > maxCount) this.minCellCount = maxCount
};
GridRegion.prototype.handleRegionMouseClick = function (mousePosition) {
    Game.virtualRegion.setRegion(this, false);
    Game.virtualRegion.handleRegionMouseClick(mousePosition)
};
GridRegion.prototype.renderRegion = function (context) {
    if (!Game.virtualRegion.isRegionSet(this)) Game.virtualRegion.setRegion(this, false);
    Game.virtualRegion.renderRegion(context)
};
var GRID_REGION_VECTOR = new Vector(0, 0);
GridRegion.prototype.renderRegionBackground = function (context) {
    if (this.isRegionActive() && !Game.regionRepresentation.isZoomedOutPastCells()) return;
    var regionWidth = Settings.region.numCellColsInRegion * Settings.cell.width,
        regionHeight = Settings.region.numCellColsInRegion * Settings.cell.height;
    Game.projection.worldToScreen(this.regionOrigin, GRID_REGION_VECTOR);
    var leftX = GRID_REGION_VECTOR.x + 2,
        topY = GRID_REGION_VECTOR.y + 2;
    GRID_REGION_VECTOR.copy(this.regionOrigin);
    GRID_REGION_VECTOR.addXY(regionWidth, regionHeight);
    Game.projection.worldToScreen(GRID_REGION_VECTOR, GRID_REGION_VECTOR);
    var rightX = GRID_REGION_VECTOR.x - 2,
        bottomY = GRID_REGION_VECTOR.y - 2;
    context.save();
    context.beginPath();
    context.moveTo(leftX, topY);
    context.lineTo(leftX, bottomY);
    context.lineTo(rightX, bottomY);
    context.lineTo(rightX, topY);
    context.closePath();
    context.restore();
    context.fillStyle = this.getFillStyle();
    context.fill();
    if (Game.autoPurchase.getCurrentRegion() == this) {
        var focusLeftX = rightX - 35,
            focusRightX = rightX - 4,
            focusTopY = bottomY - 35,
            focusBottomY =
            bottomY - 4;
        context.save();
        context.beginPath();
        context.moveTo(focusLeftX, focusTopY);
        context.lineTo(focusLeftX, focusBottomY);
        context.lineTo(focusRightX, focusBottomY);
        context.lineTo(focusRightX, focusTopY);
        context.closePath();
        context.restore();
        context.fillStyle = "#222222";
        context.fill()
    }
    if (this.isVictoryRegion() && Game.grid.isGridFilled()) {
        var victoryLeftX = rightX - 35,
            victoryRightX = rightX - 4,
            victoryTopY = bottomY - 35,
            victoryBottomY = bottomY - 4;
        context.save();
        context.beginPath();
        context.moveTo(victoryRightX, victoryTopY);
        context.lineTo(victoryLeftX, victoryBottomY);
        context.lineTo(victoryRightX, victoryBottomY);
        context.closePath();
        context.restore();
        context.fillStyle = "#222222";
        context.fill()
    }
};
GridRegion.prototype.getFillStyle = function () {
    var activeColumnBonus = this.isColumnBonusRegion() && this.isColumnBonusActive(),
        activeRowBonus = this.isRowBonusRegion() && this.isRowBonusActive();
    if (activeColumnBonus || activeRowBonus) return this.getZone().getBonusColors().getFillStyle(this);
    else return this.getZone().getColors().getFillStyle(this)
};
GridRegion.prototype.isColumnBonusRegion = function () {
    return this.regionBonusType == BonusType.COLUMN_BONUS
};
GridRegion.prototype.isRowBonusRegion = function () {
    return this.regionBonusType == BonusType.ROW_BONUS
};
GridRegion.prototype.isVictoryRegion = function () {
    return this.regionBonusType == BonusType.VICTORY
};
GridRegion.prototype.setRegionBonusType = function (regionBonusType) {
    this.regionBonusType = regionBonusType
};
GridRegion.prototype.isColumnBonusActive = function () {
    if (!this.regionUnlocked || !this.regionFilled) return false;
    return Game.grid.isColumnBonusActive(this.regionCol)
};
GridRegion.prototype.isRowBonusActive = function () {
    if (!this.regionUnlocked || !this.regionFilled) return false;
    return Game.grid.isRowBonusActive(this.regionRow)
};
GridRegion.prototype.isRightMostColumn = function () {
    return this.regionCol == this.zone.getOriginRegionCol() + Settings.region.numRegionColsInZone - 1
};
GridRegion.prototype.isBottomMostRow = function () {
    return this.regionRow == this.zone.getOriginRegionRow() + Settings.region.numRegionRowsInZone - 1
};

function VirtualRegion() {
    this.region = null;
    this.cellsInRegion = this.createRegionCells();
    this.cellsInitialized = false;
    this.settingRegionForAutoPurchase = false;
    this.staticCellCounts = this.createStaticCellCounts()
}
VirtualRegion.prototype = new BaseModel;
VirtualRegion.prototype.setRegion = function (region, settingRegionForAutoPurchase) {
    this.region = region;
    this.settingRegionForAutoPurchase = settingRegionForAutoPurchase;
    this.cellsInitialized = false
};
VirtualRegion.prototype.isRegionSet = function (region) {
    return this.region === region
};
VirtualRegion.prototype.ensureCellInitialization = function () {
    if (this.cellsInitialized) return;
    this.cellsInitialized = true;
    var col, row, numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion,
        worldCol = this.region.getRegionCol() * Settings.region.numCellColsInRegion,
        worldRow = this.region.getRegionRow() * Settings.region.numCellRowsInRegion,
        cell, autoPurchase = this.settingRegionForAutoPurchase,
        index = 0;
    if (!this.region.isRegionActive()) {
        if (this.region.isRegionFilled()) this.setStaticCellCounts(this.region.getMaxCount());
        else this.setStaticCellCounts(0);
        for (col = 0; col < numCols; col++)
            for (row = 0; row < numRows; row++) {
                cell = this.cellsInRegion[index];
                cell.setCellStateInactive(this.region, worldCol + col, worldRow + row, this.staticCellCounts, index);
                index++
            }
    } else {
        for (col = 0; col < numCols; col++)
            for (row = 0; row < numRows; row++) {
                cell = this.cellsInRegion[index];
                cell.setCellState(this.region, worldCol + col, worldRow + row, index, autoPurchase);
                index++
            }
        this.cellsInRegion[0].setCellUnlocked(true);
        this.setCellUnlockStates()
    }
};
VirtualRegion.prototype.setCellUnlockStates = function () {
    var index = 0,
        numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion,
        col, row, cell;
    if (this.region.getMinCellCount() >= Settings.cell.neighborUnlockCount)
        for (col = 0; col < numCols; col++)
            for (row = 0; row < numRows; row++) {
                cell = this.cellsInRegion[index];
                cell.setCellUnlocked(true);
                index++
            } else
                for (col = 0; col < numCols; col++)
                    for (row = 0; row < numRows; row++) {
                        cell = this.cellsInRegion[index];
                        if (cell.getCount() >= Settings.cell.neighborUnlockCount) {
                            this.unlockNeighborCell(col -
                                1, row);
                            this.unlockNeighborCell(col + 1, row);
                            this.unlockNeighborCell(col, row - 1);
                            this.unlockNeighborCell(col, row + 1)
                        }
                        index++
                    }
};
VirtualRegion.prototype.createStaticCellCounts = function () {
    var col, row, numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion;
    var cellCounts = [];
    for (col = 0; col < numCols; col++)
        for (row = 0; row < numRows; row++) cellCounts.push(0);
    return cellCounts
};
VirtualRegion.prototype.setStaticCellCounts = function (count) {
    var col, row, numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion,
        counts = this.staticCellCounts,
        index = 0;
    for (col = 0; col < numCols; col++)
        for (row = 0; row < numRows; row++) counts[index++] = count
};
VirtualRegion.prototype.unlockNeighborCell = function (col, row) {
    var neighbor = this.getRegionCellLocal(col, row);
    if (neighbor) neighbor.setCellUnlocked(true)
};
VirtualRegion.prototype.createRegionCells = function () {
    var col, row, numCols = Settings.region.numCellColsInRegion,
        numRows = Settings.region.numCellRowsInRegion;
    var cells = [],
        index = 0;
    for (col = 0; col < numCols; col++)
        for (row = 0; row < numRows; row++) cells.push(new GridCell(index++));
    return cells
};
VirtualRegion.prototype.getRegionCellLocal = function (localCol, localRow) {
    var region = this.region;
    if (!region.isRegionUnlocked() || region.isRegionFilled()) return null;
    if (localCol < 0 || localCol >= Settings.region.numCellColsInRegion) return null;
    if (localRow < 0 || localRow >= Settings.region.numCellRowsInRegion) return null;
    this.ensureCellInitialization();
    var index = localCol * Settings.region.numCellColsInRegion + localRow;
    if (index >= 0 && index < this.cellsInRegion.length) return this.cellsInRegion[index];
    else return null
};
VirtualRegion.prototype.getCellsInRegion = function () {
    this.ensureCellInitialization();
    return this.cellsInRegion
};
VirtualRegion.prototype.regionContains = function (vector) {
    var regionOrigin = this.region.getRegionOrigin(),
        x0 = regionOrigin.x,
        y0 = regionOrigin.y,
        regionWidth = Settings.region.numCellColsInRegion * Settings.cell.width,
        regionHeight = Settings.region.numCellRowsInRegion * Settings.cell.height;
    return vector.x >= x0 && vector.x < x0 + regionWidth && (vector.y >= y0 && vector.y < y0 + regionHeight)
};
VirtualRegion.prototype.isRegionVisible = function (spaceCenter, visibleHalfWidth, visibleHalfHeight) {
    return this.isRegionVisibleX(spaceCenter, visibleHalfWidth) && this.isRegionVisibleY(spaceCenter, visibleHalfHeight)
};
VirtualRegion.prototype.isRegionVisibleX = function (spaceCenter, visibleHalfWidth) {
    var regionOrigin = this.region.getRegionOrigin(),
        minX = regionOrigin.x,
        maxX = minX + Settings.region.numCellColsInRegion * Settings.cell.width,
        minVisibleX = spaceCenter.x - visibleHalfWidth,
        maxVisibleX = spaceCenter.x + visibleHalfWidth;
    return minX < maxVisibleX && maxX >= minVisibleX
};
VirtualRegion.prototype.isRegionVisibleY = function (spaceCenter, visibleHalfHeight) {
    var regionOrigin = this.region.getRegionOrigin(),
        minY = regionOrigin.y,
        maxY = minY + Settings.region.numCellRowsInRegion * Settings.cell.height,
        minVisibleY = spaceCenter.y - visibleHalfHeight,
        maxVisibleY = spaceCenter.y + visibleHalfHeight;
    return minY < maxVisibleY && maxY >= minVisibleY
};
VirtualRegion.prototype.calculateMoneyPerTick = function () {
    var i, mpt = 0;
    if (this.region.isRegionUnlocked()) {
        this.ensureCellInitialization();
        for (i = 0; i < this.cellsInRegion.length; i++) mpt += this.cellsInRegion[i].getMoneyPerTick()
    }
    return mpt
};
VirtualRegion.prototype.calculateRegionScore = function () {
    var cell, i, totalCellCost = 0,
        totalMoneyPerSecond = 0;
    this.ensureCellInitialization();
    var maxCount = this.region.getMaxCount();
    for (i = 0; i < this.cellsInRegion.length; i++) {
        cell = this.cellsInRegion[i];
        totalCellCost += cell.getCostForTotalCount(maxCount);
        totalMoneyPerSecond += cell.calculateRateForCount(maxCount)
    }
    var regionScore = totalMoneyPerSecond / totalCellCost;
    this.region.setRegionScore(regionScore);
    return regionScore
};
VirtualRegion.prototype.handleRegionMouseClick = function (mousePosition) {
    if (!this.region.isRegionUnlocked()) return;
    var i, cell;
    if (Game.regionRepresentation.isZoomedOutPastCells()) Game.regionRepresentation.handleClick(mousePosition, this.region);
    else if (this.region.isRegionActive()) {
        this.ensureCellInitialization();
        for (i = 0; i < this.cellsInRegion.length; i++) {
            cell = this.cellsInRegion[i];
            if (cell.isCellUnlocked() && cell.cellContains(mousePosition)) {
                cell.handleMouseClick(mousePosition);
                if (!this.region.isRegionActive()) break
            }
        }
    } else if (Game.grid.isGridFilled() &&
        this.region.isVictoryRegion()) {
        this.ensureCellInitialization();
        cell = this.cellsInRegion[this.cellsInRegion.length - 1];
        if (cell.cellContains(mousePosition)) cell.handleMouseClick(mousePosition)
    }
};
VirtualRegion.prototype.renderCellBordersAndBackgrounds = function (context) {
    this.ensureCellInitialization();
    var i, cell, projection = Game.projection,
        visibleHalfWidth = projection.screenToWorldDistance(Game.canvas.width / 2),
        visibleHalfHeight = projection.screenToWorldDistance(Game.canvas.height / 2),
        gridCenter = projection.getGridCenter();
    for (i = 0; i < this.cellsInRegion.length; i++) {
        cell = this.cellsInRegion[i];
        if (cell.isCellVisible(gridCenter, visibleHalfWidth, visibleHalfHeight)) cell.renderCellBackground(context)
    }
};
VirtualRegion.prototype.renderRegion = function (context) {
    if (Game.regionRepresentation.isZoomedOutPastCells()) Game.regionRepresentation.representRegion(context, this.region);
    else {
        this.ensureCellInitialization();
        var i, cell, projection = Game.projection,
            visibleHalfWidth = projection.screenToWorldDistance(Game.canvas.width / 2),
            visibleHalfHeight = projection.screenToWorldDistance(Game.canvas.height / 2),
            gridCenter = projection.getGridCenter();
        for (i = 0; i < this.cellsInRegion.length; i++) {
            cell = this.cellsInRegion[i];
            if (cell.isCellVisible(gridCenter,
                    visibleHalfWidth, visibleHalfHeight)) cell.renderCell(context)
        }
    }
};

function GridZone(originRegionCol, originRegionRow, balanceSettings, colors, bonusColors, bottomZoneRow, rightZoneCol) {
    this.gridOrigin = new Vector(originRegionCol * Settings.region.numCellColsInRegion * Settings.cell.width, originRegionRow * Settings.region.numCellColsInRegion * Settings.cell.height);
    this.originRegionCol = originRegionCol;
    this.originRegionRow = originRegionRow;
    this.autoPrestigeEnabled = false;
    this.zoneUnlocked = false;
    this.moneyPerTick = 0;
    this.balanceSettings = balanceSettings;
    this.colors = colors;
    this.bonusColors =
        bonusColors;
    this.filledRegionCount = 0;
    this.victoryZone = bottomZoneRow && rightZoneCol;
    this.prestigeCompletion = false;
    this.regions = this.createRegions(bottomZoneRow, rightZoneCol)
}
GridZone.prototype = new BaseModel;
GridZone.prototype.createRegions = function (bottomZoneRow, rightZoneCol) {
    var regions = [],
        originCol = this.originRegionCol,
        originRow = this.originRegionRow,
        regionCol, regionRow, region, columnBonusRegion, rowBonusRegion, endCol = originCol + Settings.region.numRegionColsInZone,
        endRow = originRow + Settings.region.numRegionRowsInZone;
    for (regionCol = originCol; regionCol < endCol; regionCol++) {
        rowBonusRegion = rightZoneCol && regionCol == endCol - 1;
        for (regionRow = originRow; regionRow < endRow; regionRow++) {
            region = new GridRegion(this, regionCol,
                regionRow);
            columnBonusRegion = bottomZoneRow && regionRow == endRow - 1;
            if (columnBonusRegion || rowBonusRegion)
                if (columnBonusRegion && rowBonusRegion) region.setRegionBonusType(BonusType.VICTORY);
                else if (columnBonusRegion) region.setRegionBonusType(BonusType.COLUMN_BONUS);
            else if (rowBonusRegion) region.setRegionBonusType(BonusType.ROW_BONUS);
            regions.push(region)
        }
    }
    return regions
};
GridZone.prototype.resetState = function () {
    this.zoneUnlocked = false;
    this.moneyPerTick = 0;
    this.autoPrestigeEnabled = false;
    this.filledRegionCount = 0;
    this.prestigeCompletion = false;
    var i;
    for (i = 0; i < this.regions.length; i++) this.regions[i].resetState()
};
GridZone.prototype.onSaveLoad = function () {
    this.filledRegionCount = 0;
    var i;
    for (i = 0; i < this.regions.length; i++) {
        this.regions[i].onSaveLoad();
        if (this.regions[i].isRegionFilled()) this.filledRegionCount++
    }
    this.moneyPerTick = this.calculateMoneyPerTick();
    this.checkForPrestigeCompletion()
};
GridZone.prototype.checkForPrestigeCompletion = function () {
    this.prestigeCompletion = true;
    var i;
    for (i = 0; i < this.regions.length; i++)
        if (this.regions[i].getPrestigeCount() != 2) {
            this.prestigeCompletion = false;
            break
        }
};
GridZone.prototype.isPrestigeCompletion = function () {
    return this.prestigeCompletion
};
GridZone.prototype.getGridOrigin = function () {
    return this.gridOrigin
};
GridZone.prototype.getOriginRegionCol = function () {
    return this.originRegionCol
};
GridZone.prototype.getOriginRegionRow = function () {
    return this.originRegionRow
};
GridZone.prototype.isAutoPrestigeEnabled = function () {
    return this.autoPrestigeEnabled
};
GridZone.prototype.setAutoPrestigeEnabled = function (autoPrestigeEnabled) {
    this.autoPrestigeEnabled = autoPrestigeEnabled
};
GridZone.prototype.enableAutoPrestige = function () {
    if (!this.autoPrestigeEnabled) {
        this.autoPrestigeEnabled = true;
        var i, region;
        for (i = 0; i < this.regions.length; i++) {
            region = this.regions[i];
            if (region.isPrestigeAvailable()) region.prestigeRegion()
        }
    }
};
GridZone.prototype.disableAutoPrestige = function () {
    this.autoPrestigeEnabled = false
};
GridZone.prototype.getGridRegions = function () {
    return this.regions
};
GridZone.prototype.isZoneUnlocked = function () {
    return this.zoneUnlocked
};
GridZone.prototype.setZoneUnlocked = function (zoneUnlocked) {
    this.zoneUnlocked = zoneUnlocked
};
GridZone.prototype.onRegionFilled = function (region) {
    var regionCol = region.getRegionCol(),
        regionRow = region.getRegionRow(),
        leftRegion = this.getRegionAbsolute(regionCol - 1, regionRow),
        rightRegion = this.getRegionAbsolute(regionCol + 1, regionRow),
        topRegion = this.getRegionAbsolute(regionCol, regionRow - 1),
        bottomRegion = this.getRegionAbsolute(regionCol, regionRow + 1);
    this.filledRegionCount++;
    if (this.filledRegionCount > this.regions.length) {
        console.log("onRegionFilled filled region count too big: " + this.filledRegionCount +
            " regions.length=" + this.regions.length);
        this.filledRegionCount = this.regions.length
    }
    if (leftRegion) leftRegion.unlockRegion();
    if (rightRegion) rightRegion.unlockRegion();
    if (topRegion) topRegion.unlockRegion();
    if (bottomRegion) bottomRegion.unlockRegion();
    if (region.getPrestigeCount() == 0 && (this.isColumnFilledAbsolute(regionCol) && !this.isPrestigeEnabledRegionInColumn(regionCol))) {
        var prestigeReadyRegion = this.getRegionAbsolute(regionCol, this.getOriginRegionRow());
        prestigeReadyRegion.declarePrestigeAvailable()
    }
    if (region.getPrestigeCount() ==
        1 && this.isRowReadyForSecondPrestige(regionRow)) {
        var secondPrestigeReadyRegion = this.getRegionAbsolute(this.getOriginRegionCol(), regionRow);
        secondPrestigeReadyRegion.declarePrestigeAvailable()
    }
    if (region.getPrestigeCount() == 2) {
        this.checkForPrestigeCompletion();
        if (this.isPrestigeCompletion()) EventTracker.trackEvent("BASIC2", "Zone Prestige Completion")
    }
    Game.grid.onGridRegionFilled(this, region)
};
GridZone.prototype.isPrestigeEnabledRegionInColumn = function (regionColumn) {
    var row, localCol = regionColumn - this.originRegionCol,
        numRegionRows = Settings.region.numRegionRowsInZone,
        region;
    for (row = 0; row < numRegionRows; row++) {
        region = this.regions[localCol * numRegionRows + row];
        if (region.isPrestigeAvailable() || region.getPrestigeCount() > 0) return true
    }
    return false
};
GridZone.prototype.isRowReadyForSecondPrestige = function (regionRow) {
    var localRow = regionRow - this.originRegionRow,
        rowRegion, numRegionRows = Settings.region.numRegionRowsInZone,
        numRegionCols = Settings.region.numRegionColsInZone,
        col;
    for (col = 0; col < numRegionCols; col++) {
        rowRegion = this.regions[col * numRegionRows + localRow];
        if (!rowRegion.isRegionFilled() || (rowRegion.isPrestigeAvailable() || rowRegion.getPrestigeCount() != 1)) return false
    }
    return true
};
GridZone.prototype.onRegionPrestige = function (region) {
    this.filledRegionCount--;
    if (this.filledRegionCount < 0) {
        console.log("onRegionPrestige filled region count too small: " + this.filledRegionCount);
        this.filledRegionCount = 0
    }
    Game.grid.onGridRegionPrestige(this, region)
};
GridZone.prototype.getRegionAbsolute = function (regionCol, regionRow) {
    var localCol = regionCol - this.originRegionCol,
        localRow = regionRow - this.originRegionRow;
    return this.getRegionLocal(localCol, localRow)
};
GridZone.prototype.getRegionLocal = function (col, row) {
    var numRegionCols = Settings.region.numRegionColsInZone,
        numRegionRows = Settings.region.numRegionRowsInZone;
    if (col < 0 || col >= numRegionCols) return null;
    if (row < 0 || row >= numRegionRows) return null;
    var index = col * numRegionRows + row;
    if (index >= 0 && index < this.regions.length) return this.regions[index];
    else {
        console.log("Failed to find region. col=" + col + " row=" + row + " index=" + index);
        return null
    }
};
GridZone.prototype.getMoneyPerTick = function () {
    return this.moneyPerTick
};
GridZone.prototype.getFilledRegionPercent = function () {
    if (this.filledRegionCount == 0) return 0;
    if (this.filledRegionCount == this.regions.length) return 100;
    else return Math.round(100 * (this.filledRegionCount / this.regions.length))
};
GridZone.prototype.isZoneFilled = function () {
    return this.filledRegionCount == this.regions.length
};
GridZone.prototype.isVictoryZone = function () {
    return this.victoryZone
};
GridZone.prototype.getBalanceSettings = function () {
    return this.balanceSettings
};
GridZone.prototype.onCellPurchase = function () {
    this.moneyPerTick = this.calculateMoneyPerTick();
    Game.grid.onCellPurchase()
};
GridZone.prototype.calculateMoneyPerTick = function () {
    var i, mpt = 0;
    for (i = 0; i < this.regions.length; i++) mpt += this.regions[i].getMoneyPerTick();
    return mpt
};
GridZone.prototype.gridContains = function (vector) {
    var x0 = this.gridOrigin.x,
        y0 = this.gridOrigin.y,
        gridWidth = Settings.region.numRegionColsInZone * Settings.region.numCellColsInRegion * Settings.cell.width,
        gridHeight = Settings.region.numRegionRowsInZone * Settings.region.numCellRowsInRegion * Settings.cell.height;
    return vector.x >= x0 && vector.x < x0 + gridWidth && (vector.y >= y0 && vector.y < y0 + gridHeight)
};
GridZone.prototype.isZoneVisible = function (spaceCenter, visibleHalfWidth, visibleHalfHeight) {
    return this.isZoneVisibleX(spaceCenter, visibleHalfWidth) && this.isZoneVisibleY(spaceCenter, visibleHalfHeight)
};
GridZone.prototype.isZoneVisibleX = function (spaceCenter, visibleHalfWidth) {
    var minX = this.gridOrigin.x,
        maxX = minX + Settings.region.numRegionColsInZone * Settings.region.numCellColsInRegion * Settings.cell.width,
        minVisibleX = spaceCenter.x - visibleHalfWidth,
        maxVisibleX = spaceCenter.x + visibleHalfWidth;
    return minX < maxVisibleX && maxX >= minVisibleX
};
GridZone.prototype.isZoneVisibleY = function (spaceCenter, visibleHalfHeight) {
    var minY = this.gridOrigin.y,
        maxY = minY + Settings.region.numRegionRowsInZone * Settings.region.numCellRowsInRegion * Settings.cell.height,
        minVisibleY = spaceCenter.y - visibleHalfHeight,
        maxVisibleY = spaceCenter.y + visibleHalfHeight;
    return minY < maxVisibleY && maxY >= minVisibleY
};
GridZone.prototype.handleMouseClick = function (mousePosition) {
    if (this.gridContains(mousePosition))
        if (Game.gridRepresentation.isZoomedOutPastRegions()) {
            Game.gridRepresentation.handleClick(mousePosition, this);
            return true
        } else {
            var i, region;
            for (i = 0; i < this.regions.length; i++) {
                region = this.regions[i];
                if (region.regionContains(mousePosition)) {
                    region.handleRegionMouseClick(mousePosition);
                    return true
                }
            }
        }
    return false
};
GridZone.prototype.onNeighborGridRegionFilled = function (zone, region) {
    if (this.isZoneUnlocked()) return;
    if (region.getPrestigeCount() > 0) return;
    var leftNeighbor = this.isLeftNeighbor(zone) && this.originRegionRow == zone.getOriginRegionRow(),
        topNeighbor = this.isTopNeighbor(zone) && this.originRegionCol == zone.getOriginRegionCol();
    if (!leftNeighbor && !topNeighbor) return;
    if (leftNeighbor && !region.isRightMostColumn()) return;
    else if (topNeighbor && !region.isBottomMostRow()) return;
    this.setZoneUnlocked(true);
    EventTracker.trackEvent("BASIC2",
        "Zone Unlocked");
    this.regions[0].unlockRegion()
};
GridZone.prototype.isLeftNeighbor = function (zone) {
    return this.originRegionCol == zone.getOriginRegionCol() + Settings.region.numRegionColsInZone
};
GridZone.prototype.isTopNeighbor = function (zone) {
    return this.originRegionRow == zone.getOriginRegionRow() + Settings.region.numRegionRowsInZone
};
GridZone.prototype.isColumnFilledAbsolute = function (regionColumn) {
    return this.isColumnFilledLocal(regionColumn - this.originRegionCol)
};
GridZone.prototype.isRowFilledAbsolute = function (regionRow) {
    return this.isRowFilledLocal(regionRow - this.originRegionRow)
};
GridZone.prototype.isColumnFilledLocal = function (col) {
    var colRegion, numRegionRows = Settings.region.numRegionRowsInZone,
        row;
    for (row = 0; row < numRegionRows; row++) {
        colRegion = this.regions[col * numRegionRows + row];
        if (!colRegion.isRegionFilled()) return false
    }
    return true
};
GridZone.prototype.isRowFilledLocal = function (row) {
    var rowRegion, numRegionRows = Settings.region.numRegionRowsInZone,
        numRegionCols = Settings.region.numRegionColsInZone,
        col;
    for (col = 0; col < numRegionCols; col++) {
        rowRegion = this.regions[col * numRegionRows + row];
        if (!rowRegion.isRegionFilled()) return false
    }
    return true
};
GridZone.prototype.getColors = function () {
    return this.colors
};
GridZone.prototype.getBonusColors = function () {
    return this.bonusColors
};
var GRID_VECTOR = new Vector(0, 0);
GridZone.prototype.renderGridBackground = function (context) {
    if (!this.zoneUnlocked) return;
    var gridWidth = Settings.region.numRegionColsInZone * Settings.region.numCellColsInRegion * Settings.cell.width,
        gridHeight = Settings.region.numRegionRowsInZone * Settings.region.numCellColsInRegion * Settings.cell.height;
    Game.projection.worldToScreen(this.gridOrigin, GRID_VECTOR);
    var leftX = GRID_VECTOR.x + 2,
        topY = GRID_VECTOR.y + 2;
    GRID_VECTOR.copy(this.gridOrigin);
    GRID_VECTOR.addXY(gridWidth, gridHeight);
    Game.projection.worldToScreen(GRID_VECTOR,
        GRID_VECTOR);
    var rightX = GRID_VECTOR.x - 2,
        bottomY = GRID_VECTOR.y - 2;
    context.save();
    context.beginPath();
    context.moveTo(leftX, topY);
    context.lineTo(leftX, bottomY);
    context.lineTo(rightX, bottomY);
    context.lineTo(rightX, topY);
    context.closePath();
    context.restore();
    if (this.isPrestigeCompletion()) context.fillStyle = this.colors.getSecondPrestigeVeteranColor();
    else context.fillStyle = this.colors.getFilledColor();
    context.fill();
    var currentRegion = Game.autoPurchase.getCurrentRegion();
    if (currentRegion && currentRegion.getZone() ==
        this) {
        var focusLeftX = rightX - 35,
            focusRightX = rightX - 4,
            focusTopY = bottomY - 35,
            focusBottomY = bottomY - 4;
        context.save();
        context.beginPath();
        context.moveTo(focusLeftX, focusTopY);
        context.lineTo(focusLeftX, focusBottomY);
        context.lineTo(focusRightX, focusBottomY);
        context.lineTo(focusRightX, focusTopY);
        context.closePath();
        context.restore();
        context.fillStyle = "#222222";
        context.fill()
    }
    if (this.victoryZone && Game.grid.isGridFilled()) {
        var victoryLeftX = rightX - 35,
            victoryRightX = rightX - 4,
            victoryTopY = bottomY - 35,
            victoryBottomY =
            bottomY - 4;
        context.save();
        context.beginPath();
        context.moveTo(victoryRightX, victoryTopY);
        context.lineTo(victoryLeftX, victoryBottomY);
        context.lineTo(victoryRightX, victoryBottomY);
        context.closePath();
        context.restore();
        context.fillStyle = "#222222";
        context.fill()
    }
};
GridZone.prototype.renderZone = function (context) {
    Game.gridRepresentation.representZone(context, this)
};

function Grid() {
    this.gridZones = [];
    this.columnBonusActive = [];
    this.rowBonusActive = [];
    this.moneyPerTick = 0;
    this.bonusMultiplier = 1;
    this.gridFilled = false;
    this.gridCols = Settings.grid.numZoneCols;
    this.gridRows = Settings.grid.numZoneRows
}
Grid.prototype = new BaseModel;
Grid.prototype.buildZones = function () {
    var victories = Game.total.getVictories();
    console.log("Grid.buildZones - using victory count: " + victories);
    this.gridCols = Settings.grid.numZoneCols + victories;
    this.gridRows = Settings.grid.numZoneRows + victories;
    var cols = this.gridCols,
        rows = this.gridRows,
        zoneCol, zoneRow, regionCol, regionRow;
    var size = cols * rows;
    var evenColors = new Colors(Settings.colors.evenColors),
        oddColors = new Colors(Settings.colors.oddColors),
        evenCol, evenRow, evenZone = true;
    if (this.gridZones.length != size) {
        console.log("Grid.buildZones - replacing " +
            this.gridZones.length + " zones with " + size);
        this.gridZones.length = 0;
        this.columnBonusActive.length = 0;
        this.rowBonusActive.length = 0;
        for (zoneCol = 0; zoneCol < cols; zoneCol++) {
            evenCol = zoneCol % 2 == 0;
            for (zoneRow = 0; zoneRow < rows; zoneRow++) {
                evenRow = zoneRow % 2 == 0;
                evenZone = evenCol && evenRow || !evenCol && !evenRow;
                this.gridZones.push(new GridZone(zoneCol * Settings.region.numRegionColsInZone, zoneRow * Settings.region.numRegionRowsInZone, new BalanceSettings(zoneCol, zoneRow), evenZone ? evenColors : oddColors, new Colors(Settings.colors.bonusRegion),
                    zoneRow == rows - 1, zoneCol == cols - 1))
            }
        }
        for (zoneCol = 0; zoneCol < cols; zoneCol++)
            for (regionCol = 0; regionCol < Settings.region.numRegionColsInZone; regionCol++) this.columnBonusActive.push(false);
        for (zoneRow = 0; zoneRow < rows; zoneRow++)
            for (regionRow = 0; regionRow < Settings.region.numRegionRowsInZone; regionRow++) this.rowBonusActive.push(false)
    } else console.log("Grid.buildZones - correct zone array length, no need to create anything.")
};
Grid.prototype.getZonesForLoading = function () {
    this.buildZones();
    return this.gridZones
};
Grid.prototype.getGridCols = function () {
    return this.gridCols
};
Grid.prototype.getGridRows = function () {
    return this.gridRows
};
Grid.prototype.resetState = function () {
    this.bonusMultiplier = 1;
    this.moneyPerTick = 0;
    this.gridFilled = false;
    console.log("Grid.resetState - building zones");
    this.buildZones();
    console.log("Grid.resetState - resetting state of " + this.gridZones.length + " zones.");
    var i;
    for (i = 0; i < this.gridZones.length; i++) this.gridZones[i].resetState();
    for (i = 0; i < this.columnBonusActive.length; i++) this.columnBonusActive[i] = false;
    for (i = 0; i < this.rowBonusActive.length; i++) this.rowBonusActive[i] = false;
    this.gridZones[0].setZoneUnlocked(true);
    this.gridZones[0].getGridRegions()[0].unlockRegion()
};
Grid.prototype.onSaveLoad = function () {
    console.log("Grid.onSaveLoad - invoking onSaveLoad on " + this.gridZones.length + " zones.");
    var i;
    for (i = 0; i < this.gridZones.length; i++) this.gridZones[i].onSaveLoad();
    this.updateGridFilledStatus();
    this.checkForBonusActivationsOnSaveLoad();
    this.calculateBonusMultiplier();
    this.calculateMoneyPerTick()
};
Grid.prototype.checkForBonusActivationsOnSaveLoad = function () {
    var col, row, numCols = this.gridCols * Settings.region.numRegionColsInZone,
        numRows = this.gridRows * Settings.region.numRegionRowsInZone,
        zone;
    for (col = 0; col < numCols; col++) {
        zone = this.getZoneForRegionCol(col);
        if (this.isGridColumnFilled(zone, col)) this.columnBonusActive[col] = true
    }
    for (row = 0; row < numRows; row++) {
        zone = this.getZoneForRegionRow(row);
        if (this.isGridRowFilled(zone, row)) this.rowBonusActive[row] = true
    }
};
Grid.prototype.getZoneForRegionCol = function (regionCol) {
    var i, zone, col, numCols = Settings.region.numRegionColsInZone;
    for (i = 0; i < this.gridZones.length; i++) {
        zone = this.gridZones[i];
        if (zone.getOriginRegionRow() > 0) continue;
        col = zone.getOriginRegionCol();
        if (col <= regionCol && regionCol < col + numCols) return zone
    }
    console.log("getZoneForRegionCol failed to find zone for col: " + regionCol);
    return null
};
Grid.prototype.getZoneForRegionRow = function (regionRow) {
    var i, zone, row, numRows = Settings.region.numRegionRowsInZone;
    for (i = 0; i < this.gridZones.length; i++) {
        zone = this.gridZones[i];
        if (zone.getOriginRegionCol() > 0) continue;
        row = zone.getOriginRegionRow();
        if (row <= regionRow && regionRow < row + numRows) return zone
    }
    console.log("getZoneForRegionRow failed to find zone for row: " + regionRow);
    return null
};
Grid.prototype.isGridFilled = function () {
    return this.gridFilled
};
Grid.prototype.getGridZones = function () {
    return this.gridZones
};
Grid.prototype.getMoneyPerTick = function () {
    return this.moneyPerTick
};
Grid.prototype.getBonusMultiplier = function () {
    return this.bonusMultiplier
};
Grid.prototype.onCellPurchase = function () {
    this.calculateMoneyPerTick()
};
Grid.prototype.calculateMoneyPerTick = function () {
    var i, mpt = 0;
    for (i = 0; i < this.gridZones.length; i++) mpt += this.gridZones[i].getMoneyPerTick();
    this.moneyPerTick = mpt
};
Grid.prototype.calculateBonusMultiplier = function () {
    var i, bonus = 1,
        perColumnBonus = Settings.balance.columnBonusMultiplier,
        perRowBonus = Settings.balance.rowBonusMultiplier;
    for (i = 0; i < this.columnBonusActive.length; i++)
        if (this.columnBonusActive[i]) bonus *= perColumnBonus;
    for (i = 0; i < this.rowBonusActive.length; i++)
        if (this.rowBonusActive[i]) bonus *= perRowBonus;
    this.bonusMultiplier = bonus
};
Grid.prototype.handleMouseClick = function (mousePosition) {
    var i, grid;
    for (i = 0; i < this.gridZones.length; i++) {
        grid = this.gridZones[i];
        if (grid.handleMouseClick(mousePosition)) break
    }
};
Grid.prototype.onGridRegionFilled = function (zone, region) {
    var activatedBonus = false;
    if (this.isGridColumnFilled(zone, region.getRegionCol())) {
        this.columnBonusActive[region.getRegionCol()] = true;
        activatedBonus = true
    }
    if (this.isGridRowFilled(zone, region.getRegionRow())) {
        this.rowBonusActive[region.getRegionRow()] = true;
        activatedBonus = true
    }
    if (activatedBonus) this.calculateBonusMultiplier();
    var i, currentZone;
    for (i = 0; i < this.gridZones.length; i++) {
        currentZone = this.gridZones[i];
        if (currentZone != zone && !currentZone.isZoneUnlocked()) currentZone.onNeighborGridRegionFilled(zone,
            region)
    }
    if (zone.isZoneFilled()) this.updateGridFilledStatus()
};
Grid.prototype.updateGridFilledStatus = function () {
    this.gridFilled = true;
    var i;
    for (i = 0; i < this.gridZones.length; i++)
        if (!this.gridZones[i].isZoneFilled()) {
            this.gridFilled = false;
            break
        }
};
Grid.prototype.onGridRegionPrestige = function (zone, region) {
    var deactivatedBonus = false;
    if (this.isColumnBonusActive(region.getRegionCol())) {
        this.columnBonusActive[region.getRegionCol()] = false;
        deactivatedBonus = true
    }
    if (this.isRowBonusActive(region.getRegionRow())) {
        this.rowBonusActive[region.getRegionRow()] = false;
        deactivatedBonus = true
    }
    if (deactivatedBonus) this.calculateBonusMultiplier();
    this.gridFilled = false
};
Grid.prototype.isColumnBonusActive = function (regionColAbs) {
    return this.columnBonusActive[regionColAbs]
};
Grid.prototype.isRowBonusActive = function (regionRowAbs) {
    return this.rowBonusActive[regionRowAbs]
};
Grid.prototype.isGridColumnFilled = function (zone, regionColAbs) {
    var zoneCol = zone.getOriginRegionCol(),
        currentZone, i;
    for (i = 0; i < this.gridZones.length; i++) {
        currentZone = this.gridZones[i];
        if (currentZone.getOriginRegionCol() != zoneCol) continue;
        if (!currentZone.isColumnFilledAbsolute(regionColAbs)) return false
    }
    return true
};
Grid.prototype.isGridRowFilled = function (zone, regionRowAbs) {
    var zoneRow = zone.getOriginRegionRow(),
        currentZone, i;
    for (i = 0; i < this.gridZones.length; i++) {
        currentZone = this.gridZones[i];
        if (currentZone.getOriginRegionRow() != zoneRow) continue;
        if (!currentZone.isRowFilledAbsolute(regionRowAbs)) return false
    }
    return true
};
Grid.prototype.getFirstUnfilledColumn = function () {
    var col;
    for (col = 0; col < this.columnBonusActive.length; col++)
        if (!this.columnBonusActive[col]) return col;
    return -1
};
Grid.prototype.getFirstUnfilledRow = function () {
    var row;
    for (row = 0; row < this.rowBonusActive.length; row++)
        if (!this.rowBonusActive[row]) return row;
    return -1
};

function AutoPurchaseFollower() {
    this.autoPanEnabled = false;
    this.regionCenter = new Vector(0, 0);
    this.targetVector = new Vector(0, 0);
    this.frameDeltaVector = new Vector(0, 0);
    this.destinationReachedRegion = null
}
AutoPurchaseFollower.prototype.resetFollower = function () {
    this.autoPanEnabled = false;
    this.destinationReachedRegion = null
};
AutoPurchaseFollower.prototype.isAutoPanEnabled = function () {
    return this.autoPanEnabled
};
AutoPurchaseFollower.prototype.setAutoPanEnabled = function (autoPanEnabled) {
    if (!this.autoPanEnabled && autoPanEnabled) this.destinationReachedRegion = null;
    this.autoPanEnabled = autoPanEnabled
};
AutoPurchaseFollower.prototype.updateFollowerForFrame = function () {
    if (!this.autoPanEnabled) return;
    var currentRegion = Game.autoPurchase.getCurrentRegion();
    if (!currentRegion) return;
    if (this.destinationReachedRegion == currentRegion) return;
    this.regionCenter.copy(currentRegion.getRegionOrigin());
    this.regionCenter.addXY(Settings.region.numCellColsInRegion * Settings.cell.width / 2, Settings.region.numCellColsInRegion * Settings.cell.height / 2);
    var gridCenter = Game.projection.getGridCenter();
    var distanceToTarget = gridCenter.distance(this.regionCenter);
    if (distanceToTarget < Settings.cell.width / 10) {
        this.destinationReachedRegion = currentRegion;
        return
    }
    this.targetVector.copy(this.regionCenter);
    this.targetVector.subtract(gridCenter);
    this.targetVector.scale(0.09);
    this.frameDeltaVector.copy(gridCenter);
    this.frameDeltaVector.add(this.targetVector);
    Game.projection.setGridCenter(this.frameDeltaVector.x, this.frameDeltaVector.y)
};
var AutoPurchaseMode = {
    MANUAL: 0,
    AUTOMATIC: 1,
    COLUMN_FOCUS: 2,
    ROW_FOCUS: 3,
    PRESTIGE_FOCUS: 4,
    SINGLE: 5
};

function AutoPurchaseSettings() {
    this.autoPurchaseMode = AutoPurchaseMode.MANUAL;
    this.autoPurchaseRegionCount = 0
}
AutoPurchaseSettings.prototype.resetSettings = function () {
    this.autoPurchaseRegionCount = 0;
    this.autoPurchaseMode = AutoPurchaseMode.MANUAL;
    this.setAutoPurchasingInAllRegions(false)
};
AutoPurchaseSettings.prototype.onSaveLoad = function () {
    this.applyAutoPurchaseMode()
};
AutoPurchaseSettings.prototype.incrementAutoPurchaseRegionCount = function () {
    this.autoPurchaseRegionCount++
};
AutoPurchaseSettings.prototype.decrementAutoPurchaseRegionCount = function () {
    this.autoPurchaseRegionCount--;
    if (this.autoPurchaseRegionCount < 0) this.autoPurchaseRegionCount = 0
};
AutoPurchaseSettings.prototype.getAutoPurchaseRegionCount = function () {
    return this.autoPurchaseRegionCount
};
AutoPurchaseSettings.prototype.getAutoPurchaseMode = function () {
    return this.autoPurchaseMode
};
AutoPurchaseSettings.prototype.setAutoPurchaseMode = function (autoPurchaseMode) {
    if (this.autoPurchaseMode == autoPurchaseMode) return;
    this.autoPurchaseMode = autoPurchaseMode;
    this.applyAutoPurchaseMode();
    Game.autoPurchase.onAutoPurchaseModeChange()
};
AutoPurchaseSettings.prototype.applyAutoPurchaseMode = function () {
    if (this.autoPurchaseMode == AutoPurchaseMode.SINGLE) this.setAutoPurchasingInAllRegions(false);
    else if (this.autoPurchaseMode == AutoPurchaseMode.AUTOMATIC) this.setAutoPurchasingInAllRegions(true);
    else if (this.autoPurchaseMode == AutoPurchaseMode.PRESTIGE_FOCUS) this.setAutoPurchasingInPrestigeRegions();
    else if (this.autoPurchaseMode == AutoPurchaseMode.COLUMN_FOCUS) this.setAutoPurchasingInFirstUnfilledColumn();
    else if (this.autoPurchaseMode == AutoPurchaseMode.ROW_FOCUS) this.setAutoPurchasingInFirstUnfilledRow()
};
AutoPurchaseSettings.prototype.setAutoPurchasingInAllRegions = function (enabled) {
    var zones = Game.grid.getGridZones(),
        zone, regions, region, i, j;
    for (i = 0; i < zones.length; i++) {
        zone = zones[i];
        if (zone.isZoneUnlocked()) {
            regions = zone.getGridRegions();
            for (j = 0; j < regions.length; j++) {
                region = regions[j];
                if (region.isRegionActive()) region.setAutoPurchaseEnabled(enabled)
            }
        }
    }
};
AutoPurchaseSettings.prototype.setAutoPurchasingInPrestigeRegions = function () {
    var zones = Game.grid.getGridZones(),
        zone, regions, region, i, j;
    for (i = 0; i < zones.length; i++) {
        zone = zones[i];
        if (zone.isZoneUnlocked()) {
            regions = zone.getGridRegions();
            for (j = 0; j < regions.length; j++) {
                region = regions[j];
                if (region.isRegionActive()) region.setAutoPurchaseEnabled(region.getPrestigeCount() > 0)
            }
        }
    }
};
AutoPurchaseSettings.prototype.setAutoPurchasingInFirstUnfilledColumn = function () {
    var firstUnfilledColumn = Game.grid.getFirstUnfilledColumn(),
        zones = Game.grid.getGridZones(),
        zone, regions, region, i, j;
    if (firstUnfilledColumn < 0) return;
    for (i = 0; i < zones.length; i++) {
        zone = zones[i];
        if (zone.isZoneUnlocked()) {
            regions = zone.getGridRegions();
            for (j = 0; j < regions.length; j++) {
                region = regions[j];
                if (region.isRegionActive()) region.setAutoPurchaseEnabled(region.getRegionCol() == firstUnfilledColumn)
            }
        }
    }
};
AutoPurchaseSettings.prototype.setAutoPurchasingInFirstUnfilledRow = function () {
    var firstUnfilledRow = Game.grid.getFirstUnfilledRow(),
        zones = Game.grid.getGridZones(),
        zone, regions, region, i, j;
    if (firstUnfilledRow < 0) return;
    for (i = 0; i < zones.length; i++) {
        zone = zones[i];
        if (zone.isZoneUnlocked()) {
            regions = zone.getGridRegions();
            for (j = 0; j < regions.length; j++) {
                region = regions[j];
                if (region.isRegionActive()) region.setAutoPurchaseEnabled(region.getRegionRow() == firstUnfilledRow)
            }
        }
    }
};

function AutoPurchase() {
    this.currentRegion = null;
    this.currentRegionTime = 0
}
AutoPurchase.prototype.getCurrentRegion = function () {
    return this.currentRegion
};
AutoPurchase.prototype.onAutoPurchaseModeChange = function () {
    this.currentRegion = null
};
AutoPurchase.prototype.resetState = function () {
    this.currentRegion = null;
    this.currentRegionTime = 0
};
AutoPurchase.prototype.onSaveLoad = function () {};
AutoPurchase.prototype.makePurchasesForFrame = function () {
    var region = this.selectRegion();
    if (region) this.purchaseFromRegion(region)
};
AutoPurchase.prototype.selectRegion = function () {
    var currentRegionViable = this.currentRegion && (this.currentRegion.isAutoPurchaseEnabled() && this.currentRegion.isRegionActive()),
        currentRegionExpired = Date.now() - this.currentRegionTime >= Settings.autoPurchase.regionFocusMillis;
    if (!currentRegionViable) this.currentRegion = null;
    if (currentRegionViable && !currentRegionExpired) return this.currentRegion;
    var region = this.chooseGridRegionToTarget();
    if (region) {
        this.currentRegion = region;
        this.currentRegionTime = Date.now();
        return region
    }
    return region
};
AutoPurchase.prototype.purchaseFromRegion = function (region) {
    Game.virtualRegion.setRegion(region, true);
    var totalMoney = Game.total.getMoney(),
        regionCells = Game.virtualRegion.getCellsInRegion(),
        regionIndex = 0,
        cell, maxCount = region.getMaxCount(),
        availablePurchases;
    while (regionIndex < regionCells.length && !region.isRegionFilled()) {
        cell = regionCells[regionIndex++];
        if (!cell.isCellUnlocked() || cell.isMaxCountReached()) continue;
        availablePurchases = maxCount - cell.getCount();
        totalMoney = Game.total.getMoney();
        if (availablePurchases >=
            10 && cell.getCostForActualCount(10) <= totalMoney) cell.purchaseCells(10);
        else if (availablePurchases >= 7 && cell.getCostForActualCount(7) <= totalMoney) cell.purchaseCells(7);
        else if (availablePurchases >= 3 && cell.getCostForActualCount(3) <= totalMoney) cell.purchaseCells(3);
        else if (availablePurchases >= 1 && cell.getCostForActualCount(1) <= totalMoney) cell.purchaseCells(1)
    }
    if (region.isRegionFilled()) this.currentRegion = null
};
AutoPurchase.prototype.chooseGridRegionToTarget = function () {
    var zones = Game.grid.getGridZones(),
        zone, regions, g, region, regionScore, bestRegion = null,
        bestRegionScore = 0,
        i;
    for (g = 0; g < zones.length; g++) {
        zone = zones[g];
        if (!zone.isZoneUnlocked() || zone.isZoneFilled()) continue;
        regions = zone.getGridRegions();
        for (i = 0; i < regions.length; i++) {
            region = regions[i];
            if (!region.isRegionActive() || !region.isAutoPurchaseEnabled()) continue;
            regionScore = this.calculateGridRegionScore(region);
            if (regionScore < 0) continue;
            if (!bestRegion ||
                regionScore > bestRegionScore) {
                bestRegion = region;
                bestRegionScore = regionScore
            }
        }
    }
    return bestRegion
};
AutoPurchase.prototype.calculateGridRegionScore = function (region) {
    var regionScore = region.getRegionScore();
    if (regionScore < 0) {
        Game.virtualRegion.setRegion(region, true);
        regionScore = Game.virtualRegion.calculateRegionScore()
    }
    return regionScore
};

function InputHandler() {}
InputHandler.prototype.resetInputHandler = function () {};
InputHandler.prototype.isAppropriateHandler = function () {};
InputHandler.prototype.handleInput = function () {};

function BaseInputHandler() {}
BaseInputHandler.prototype.resetInputHandler = function () {};
BaseInputHandler.prototype.handleZoomInput = function () {
    var mouseWheelMotion = Game.userInput.getMouseWheelMotion();
    if (mouseWheelMotion < 0) Game.projection.zoomIn();
    else if (mouseWheelMotion > 0) Game.projection.zoomOut()
};

function PanningInputHandler() {
    this.delta = new Vector(0, 0)
}
PanningInputHandler.prototype = new BaseInputHandler;
PanningInputHandler.prototype.isAppropriateHandler = function () {
    return true
};
PanningInputHandler.prototype.handleInput = function () {
    if (Game.userInput.isLeftMouseDown()) {
        this.delta.x = Game.userInput.getMouseDeltaX();
        this.delta.y = Game.userInput.getMouseDeltaY()
    } else {
        this.delta.x *= Settings.input.panning.decayFactor;
        this.delta.y *= Settings.input.panning.decayFactor
    }
    var projection = Game.projection;
    projection.updateSpaceCenter(projection.screenToWorldDistance(-this.delta.x), projection.screenToWorldDistance(-this.delta.y));
    this.handleZoomInput()
};

function MouseClickInputHandler() {}
MouseClickInputHandler.prototype = new BaseInputHandler;
MouseClickInputHandler.prototype.isAppropriateHandler = function () {
    return Game.userInput.isLeftMouseClick()
};
MouseClickInputHandler.prototype.handleInput = function () {
    Game.grid.handleMouseClick(Game.worldMouse.getWorldPosition())
};

function DelegatingInputHandler() {
    this.defaultInputHandler = new PanningInputHandler;
    this.inputHandlers = [new MouseClickInputHandler, this.defaultInputHandler]
}
DelegatingInputHandler.prototype.resetInputHandler = function () {
    var i;
    for (i = 0; i < this.inputHandlers.length; i++) this.inputHandlers[i].resetInputHandler()
};
DelegatingInputHandler.prototype.isAppropriateHandler = function () {
    return true
};
DelegatingInputHandler.prototype.handleInput = function () {
    this.getInputHandler().handleInput()
};
DelegatingInputHandler.prototype.getInputHandler = function () {
    var i;
    for (i = 0; i < this.inputHandlers.length; i++)
        if (this.inputHandlers[i].isAppropriateHandler()) return this.inputHandlers[i];
    return this.defaultInputHandler
};

function SaveManager() {
    this.saveKey = "BASIC2_v1.00";
    this.lastSaveTime = Time.getCurrentTime()
}
SaveManager.prototype.deleteSave = function () {
    if (typeof localStorage != "undefined") localStorage.removeItem(this.saveKey);
    this.lastSaveTime = Time.getCurrentTime();
    EventTracker.trackEvent("BASIC2 SaveManager", "Delete")
};
SaveManager.prototype.checkForAutoSave = function () {
    if (Time.getCurrentTime() - this.lastSaveTime > Settings.save.autoSavePeriod) {
        this.saveGame();
        EventTracker.trackEvent("BASIC2 SaveManager", "Save")
    }
};
SaveManager.prototype.getLastSaveTime = function () {
    return this.lastSaveTime
};
SaveManager.prototype.saveGame = function () {
    var saveState = this.getSaveState();
    if (saveState) {
        if (typeof localStorage != "undefined") localStorage.setItem(this.saveKey, saveState);
        this.lastSaveTime = Time.getCurrentTime()
    }
};
SaveManager.prototype.loadSave = function () {
    if (typeof localStorage != "undefined") this.importSaveInternal(localStorage.getItem(this.saveKey));
    EventTracker.trackEvent("BASIC2 SaveManager", "Load")
};
SaveManager.prototype.importSave = function (compressedSaveState) {
    EventTracker.trackEvent("BASIC2 SaveManager", "Import");
    return this.importSaveInternal(compressedSaveState)
};
SaveManager.prototype.importSaveInternal = function (compressedSaveState) {
    if (compressedSaveState) {
        var jsonString = LZString.decompressFromBase64(compressedSaveState);
        if (jsonString) {
            var saveState = JSON.parse(jsonString);
            if (saveState) {
                Game.initializeAll();
                if (this.loadSaveState(saveState)) Game.onSaveLoad();
                return true
            }
        }
    }
    return false
};
SaveManager.prototype.getSaveState = function () {
    var jsonString = JSON.stringify(this.generateSaveState());
    if (jsonString) return LZString.compressToBase64(jsonString);
    else return null
};
SaveManager.prototype.generateSaveState = function () {
    if (!Game.gameInitialized) return {
        "saveKey": this.saveKey,
        "gameInitialized": false,
        "gameWon": false
    };
    return {
        "saveKey": this.saveKey,
        "gameInitialized": Game.gameInitialized,
        "gameWon": Game.gameWon,
        "tick": this.generateTickState(),
        "total": this.generateTotalState(),
        "grid": this.generateGridState(),
        "autoPurchaseSettings": this.generateAutoPurchaseSettingsState()
    }
};
SaveManager.prototype.loadSaveState = function (saveState) {
    Game.gameInitialized = saveState["gameInitialized"];
    if (!Game.gameInitialized) return true;
    var gameWon = saveState["gameWon"];
    Game.gameWon = gameWon ? gameWon : false;
    this.loadTickState(saveState["tick"]);
    this.loadTotalState(saveState["total"]);
    this.loadGridState(saveState["grid"]);
    this.loadAutoPurchaseSettingsState(saveState["autoPurchaseSettings"]);
    return true
};
SaveManager.prototype.generateAutoPurchaseSettingsState = function () {
    return {
        "autoPurchaseMode": Game.autoPurchaseSettings.getAutoPurchaseMode(),
        "autoPanEnabled": Game.autoPurchaseFollower.isAutoPanEnabled()
    }
};
SaveManager.prototype.loadAutoPurchaseSettingsState = function (autoPurchaseSettingsState) {
    if (!autoPurchaseSettingsState) {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        Game.autoPurchaseFollower.setAutoPanEnabled(false);
        return
    }
    var autoPurchaseMode = autoPurchaseSettingsState["autoPurchaseMode"],
        autoPanEnabled = autoPurchaseSettingsState["autoPanEnabled"];
    Game.autoPurchaseSettings.setAutoPurchaseMode(autoPurchaseMode ? autoPurchaseMode : AutoPurchaseMode.MANUAL);
    Game.autoPurchaseFollower.setAutoPanEnabled(autoPanEnabled ?
        autoPanEnabled : false)
};
SaveManager.prototype.generateTickState = function () {
    return {
        "lastTickTime": Game.tick.getLastTickTime(),
        "regionPrestigeCount": Game.tick.getRegionPrestigeCount()
    }
};
SaveManager.prototype.loadTickState = function (tickState) {
    if (!tickState) {
        console.log("ERROR: No tick state in save file.");
        return
    }
    var lastTickTime = tickState["lastTickTime"],
        regionPrestigeCount = tickState["regionPrestigeCount"];
    Game.tick.setLastTickTime(lastTickTime ? lastTickTime : Game.tick.getLastTickTime());
    Game.tick.setRegionPrestigeCount(regionPrestigeCount ? regionPrestigeCount : 0)
};
SaveManager.prototype.generateTotalState = function () {
    var total = Game.total;
    return {
        "money": total.getMoney(),
        "gameStartTime": total.getGameStartTime(),
        "purchaseCount": total.getPurchaseCountEnum(),
        "cellsPurchased": total.getCellsPurchased(),
        "regionsFilled": total.getRegionsFilled(),
        "victories": total.getVictories(),
        "gameEndTime": total.getGameEndTime()
    }
};
SaveManager.prototype.loadTotalState = function (totalState) {
    if (!totalState) {
        console.log("ERROR: No total state in save file.");
        return
    }
    var money = totalState["money"],
        gameStartTime = totalState["gameStartTime"],
        purchaseCount = totalState["purchaseCount"],
        cellsPurchased = totalState["cellsPurchased"],
        regionsFilled = totalState["regionsFilled"],
        victories = totalState["victories"],
        gameEndTime = totalState["gameEndTime"];
    var total = Game.total;
    total.setMoney(money ? money : total.getMoney());
    total.setGameStartTime(gameStartTime ?
        gameStartTime : total.getGameStartTime());
    total.setPurchaseCountEnum(purchaseCount);
    total.setCellsPurchased(cellsPurchased ? cellsPurchased : 0);
    total.setRegionsFilled(regionsFilled ? regionsFilled : 0);
    total.setVictories(victories ? victories : total.getVictories());
    total.setGameEndTime(gameEndTime ? gameEndTime : 0)
};
SaveManager.prototype.generateGridState = function () {
    return {
        "zones": this.generateGridZoneStateArray(Game.grid.getGridZones())
    }
};
SaveManager.prototype.loadGridState = function (gridState) {
    var grid = Game.grid;
    var zoneStateArray = gridState["zones"],
        i;
    var zones = grid.getZonesForLoading();
    if (zones.length != zoneStateArray.length) {
        console.log("SaveManager.loadGridState. ERROR zones.length=" + zones.length + " zoneStateArray.length=" + zoneStateArray.length);
        return
    }
    for (i = 0; i < zoneStateArray.length; i++) this.loadGridZoneState(zones[i], zoneStateArray[i])
};
SaveManager.prototype.generateGridZoneStateArray = function (zones) {
    var states = [],
        i;
    for (i = 0; i < zones.length; i++) states.push(this.generateGridZoneState(zones[i]));
    return states
};
SaveManager.prototype.generateGridZoneState = function (gridZone) {
    return {
        "unlocked": gridZone.isZoneUnlocked(),
        "autoPrestige": gridZone.isAutoPrestigeEnabled(),
        "regions": this.generateRegionStateArray(gridZone)
    }
};
SaveManager.prototype.loadGridZoneState = function (gridZone, gridState) {
    if (!gridState) {
        console.log("ERROR: No grid zone state in save file.");
        return
    }
    var unlocked = gridState["unlocked"],
        regionStates = gridState["regions"],
        autoPrestige = gridState["autoPrestige"],
        regions = gridZone.getGridRegions(),
        i;
    gridZone.setZoneUnlocked(unlocked ? unlocked : false);
    gridZone.setAutoPrestigeEnabled(autoPrestige ? autoPrestige : false);
    if (!regionStates) {
        console.log("SaveManager - loadGridZoneState: null region states.");
        return
    }
    if (regionStates.length !=
        regions.length) {
        console.log("SaveManager - loadGridZoneState: regions length mismatch. regions=" + regions.length + " states=" + regionStates.length);
        return
    }
    for (i = 0; i < regions.length; i++) this.loadRegionState(regions[i], regionStates[i])
};
SaveManager.prototype.generateRegionStateArray = function (grid) {
    var regionStates = [],
        regions = grid.getGridRegions(),
        i;
    for (i = 0; i < regions.length; i++) regionStates.push(this.generateRegionState(regions[i]));
    return regionStates
};
SaveManager.prototype.generateRegionState = function (region) {
    return {
        "unlocked": region.isRegionUnlocked(),
        "filled": region.isRegionFilled(),
        "autoPurchase": region.isAutoPurchaseEnabled(),
        "prestigeAvailable": region.isPrestigeAvailable(),
        "prestige": region.getPrestigeCount(),
        "cellCounts": region.getCellCounts()
    }
};
SaveManager.prototype.loadRegionState = function (region, regionState) {
    region.setRegionFilled(regionState["filled"]);
    region.setRegionUnlocked(regionState["unlocked"]);
    region.setAutoPurchaseEnabled(regionState["autoPurchase"]);
    region.setPrestigeAvailable(regionState["prestigeAvailable"]);
    region.setPrestigeCount(regionState["prestige"]);
    region.setCellCounts(regionState["cellCounts"])
};
var Page = {
    getElement: function (elementId) {
        return document.getElementById(elementId)
    },
    hideElement: function (element) {
        if (element) element.style.display = "none"
    },
    showElement: function (element) {
        if (element) element.style.display = "block"
    },
    hideElementId: function (elementId) {
        Page.hideElement(Page.getElement(elementId))
    },
    showElementId: function (elementId) {
        Page.showElement(Page.getElement(elementId))
    }
};

function View() {}
View.prototype.initializeView = function () {};
View.prototype.updateView = function () {};

function BaseView() {
    this.viewVisible = true;
    this.containerId = null;
    this.pageElementVisible = false
}
BaseView.prototype.getContainerId = function () {
    return this.containerId
};
BaseView.prototype.setContainerId = function (containerId) {
    this.containerId = containerId
};
BaseView.prototype.setPageElementVisible = function (pageElementVisible) {
    this.pageElementVisible = pageElementVisible
};
BaseView.prototype.isViewVisible = function () {
    return this.viewVisible
};
BaseView.prototype.setViewVisible = function (visible) {
    this.viewVisible = visible
};
BaseView.prototype.updateView = function () {
    if (this.containerId) {
        var viewVisible = this.isViewVisible();
        if (this.pageElementVisible != viewVisible) {
            this.pageElementVisible = viewVisible;
            if (viewVisible) Page.showElementId(this.containerId);
            else Page.hideElementId(this.containerId)
        }
        if (viewVisible) this.updateViewContents()
    }
};
BaseView.prototype.initializeView = function () {};
BaseView.prototype.updateViewContents = function () {};

function ParentView() {
    this.childViews = null
}
ParentView.prototype = new BaseView;
ParentView.prototype.clearChildViews = function () {
    if (this.childViews) this.childViews.length = 0
};
ParentView.prototype.addView = function (view) {
    if (view) {
        if (!this.childViews) this.childViews = [];
        this.childViews.push(view)
    }
};
ParentView.prototype.initializeView = function () {
    this.invokeInitializeViewOnChildren()
};
ParentView.prototype.updateViewContents = function () {
    this.updateChildViews()
};
ParentView.prototype.updateChildViews = function () {
    if (this.childViews) {
        var i;
        for (i = 0; i < this.childViews.length; i++) this.childViews[i].updateView()
    }
};
ParentView.prototype.invokeInitializeViewOnChildren = function () {
    if (this.childViews) {
        var i;
        for (i = 0; i < this.childViews.length; i++) this.childViews[i].initializeView()
    }
};

function MenuHeaderView() {
    this.setContainerId("bottomMenuHeader");
    this.displayedMoney = 0;
    this.displayedBonus = 0;
    this.displayedRate = 0;
    this.headerMoney = null;
    this.headerBonus = null;
    this.headerRate = null
}
MenuHeaderView.prototype = new BaseView;
MenuHeaderView.prototype.initializeView = function () {
    this.headerMoney = Page.getElement("headerMoney");
    this.headerRate = Page.getElement("headerRate");
    this.headerBonus = Page.getElement("headerBonus")
};
MenuHeaderView.prototype.updateViewContents = function () {
    var money = Game.total.getMoney(),
        moneyPerSecond = Game.total.getMoneyPerSecond(),
        bonus = Game.grid.getBonusMultiplier();
    if (this.displayedMoney != money) {
        this.displayedMoney = money;
        this.headerMoney.innerHTML = Formatter.format(money)
    }
    if (this.displayedRate != moneyPerSecond) {
        this.displayedRate = moneyPerSecond;
        this.headerRate.innerHTML = Formatter.format(moneyPerSecond)
    }
    if (this.displayedBonus != bonus) {
        this.displayedBonus = bonus;
        if (bonus <= 1) this.headerBonus.innerHTML =
            "0";
        else this.headerBonus.innerHTML = Formatter.format((bonus - 1) * 100)
    }
};

function NumbersView() {
    this.setContainerId("numbersContainer");
    this.cellsPurchasedDiv = null;
    this.displayedCellsPurchased = -1;
    this.regionsFilledDiv = null;
    this.displayedRegionsFilled = -1;
    this.regionsPrestigedDiv = null;
    this.displayedRegionsPrestiged = -1;
    this.victoriesDiv = null;
    this.displayedVictories = -1;
    this.tickRateLabelDiv = null;
    this.tickRateDiv = null;
    this.displayedMillisPerTick = -1;
    this.displayedMillisPerTickMode = true;
    this.moneyPerTickDiv = null;
    this.displayedMoneyPerTick = -1;
    this.moneyPerSecondDiv = null;
    this.displayedMoneyPerSecond = -1;
    this.moneyDiv = null;
    this.displayedMoney = -1;
    this.bonusDiv = null;
    this.displayedBonus = -1;
    this.timePlayedDiv = null;
    this.displayedTimePlayed = -1;
    this.displayedTimePlayedString = ""
}
NumbersView.prototype = new BaseView;
NumbersView.prototype.initializeView = function () {
    this.cellsPurchasedDiv = Page.getElement("cellsPurchased");
    this.displayedCellsPurchased = -1;
    this.regionsFilledDiv = Page.getElement("regionsFilled");
    this.displayedRegionsFilled = -1;
    this.regionsPrestigedDiv = Page.getElement("regionsPrestiged");
    this.displayedRegionsPrestiged = -1;
    this.victoriesDiv = Page.getElement("victories");
    this.displayedVictories = -1;
    this.tickRateLabelDiv = Page.getElement("tickRateLabel");
    this.tickRateDiv = Page.getElement("tickRate");
    this.displayedMillisPerTick = -1;
    this.displayedMillisPerTickMode = true;
    this.tickRateLabelDiv.innerHTML = "毫秒 / 轮";
    this.moneyPerTickDiv = Page.getElement("moneyPerTick");
    this.displayedMoneyPerTick = -1;
    this.moneyPerSecondDiv = Page.getElement("moneyPerSec");
    this.displayedMoneyPerSecond = -1;
    this.moneyDiv = Page.getElement("money");
    this.displayedMoney = -1;
    this.bonusDiv = Page.getElement("bonus");
    this.displayedBonus = -1;
    this.timePlayedDiv = Page.getElement("timePlayed");
    this.displayedTimePlayed = -1;
    this.displayedTimePlayedString = ""
};
NumbersView.prototype.updateViewContents = function () {
    var total = Game.total,
        cellsPurchased = total.getCellsPurchased(),
        regionsFilled = total.getRegionsFilled(),
        regionsPrestiged = Game.tick.getRegionPrestigeCount(),
        victories = total.getVictories(),
        moneyPerTick = total.getMoneyPerTick(),
        moneyPerSecond = total.getMoneyPerSecond(),
        bonus = Game.grid.getBonusMultiplier(),
        money = total.getMoney(),
        timePlayed = total.getPlayedTime();
    if (this.displayedMoney != money) {
        this.displayedMoney = money;
        this.moneyDiv.innerHTML = "$" + Formatter.format(money)
    }
    if (this.displayedMoneyPerSecond !=
        moneyPerSecond) {
        this.displayedMoneyPerSecond = moneyPerSecond;
        this.moneyPerSecondDiv.innerHTML = "$" + Formatter.format(moneyPerSecond)
    }
    this.updateTickRate();
    if (this.displayedMoneyPerTick != moneyPerTick) {
        this.displayedMoneyPerTick = moneyPerTick;
        this.moneyPerTickDiv.innerHTML = "$" + Formatter.format(moneyPerTick)
    }
    if (this.displayedBonus != bonus) {
        this.displayedBonus = bonus;
        if (bonus < 1) this.bonusDiv.innerHTML = "0%";
        else this.bonusDiv.innerHTML = Formatter.format((bonus - 1) * 100) + "%"
    }
    if (this.displayedCellsPurchased != cellsPurchased) {
        this.displayedCellsPurchased =
            cellsPurchased;
        this.cellsPurchasedDiv.innerHTML = Formatter.format(cellsPurchased)
    }
    if (this.displayedRegionsFilled != regionsFilled) {
        this.displayedRegionsFilled = regionsFilled;
        this.regionsFilledDiv.innerHTML = Formatter.format(regionsFilled)
    }
    if (this.displayedRegionsPrestiged != regionsPrestiged) {
        this.displayedRegionsPrestiged = regionsPrestiged;
        this.regionsPrestigedDiv.innerHTML = Formatter.format(regionsPrestiged)
    }
    if (this.displayedTimePlayed != timePlayed) {
        this.displayedTimePlayed = timePlayed;
        var formattedTime = Formatter.formatElapsedTime(timePlayed);
        if (this.displayedTimePlayedString != formattedTime) {
            this.displayedTimePlayedString = formattedTime;
            this.timePlayedDiv.innerHTML = formattedTime
        }
    }
    if (this.displayedVictories != victories) {
        this.displayedVictories = victories;
        this.victoriesDiv.innerHTML = Formatter.format(victories)
    }
};
NumbersView.prototype.updateTickRate = function () {
    var millisPerTick = Game.tick.getMillisPerTick();
    if (this.displayedMillisPerTick != millisPerTick) {
        this.displayedMillisPerTick = millisPerTick;
        var millisPerTickMode = millisPerTick > 100;
        if (this.displayedMillisPerTickMode != millisPerTickMode) {
            this.displayedMillisPerTickMode = millisPerTickMode;
            if (millisPerTickMode) this.tickRateLabelDiv.innerHTML = "毫秒 / 轮";
            else this.tickRateLabelDiv.innerHTML = "轮 / 秒"
        }
        if (millisPerTickMode) this.tickRateDiv.innerHTML = Formatter.format(millisPerTick);
        else {
            var ticksPerSecond = 1E3 / millisPerTick;
            if (ticksPerSecond < 100) this.tickRateDiv.innerHTML = ticksPerSecond.toFixed(1);
            else this.tickRateDiv.innerHTML = Formatter.format(ticksPerSecond)
        }
    }
};

function AutoPurchaseSettingsView() {
    this.setContainerId("autoPurchaseSettingsContainer");
    this.autoPanCheckbox = null;
    this.autoPanEnabled = false;
    this.manualModeCheckbox = null;
    this.automaticModeCheckbox = null;
    this.prestigeModeCheckbox = null;
    this.columnModeCheckbox = null;
    this.rowModeCheckbox = null;
    this.singleRegionModeCheckbox = null;
    this.enabledRegionsSpan = null;
    this.autoPurchaseMode = AutoPurchaseMode.MANUAL;
    this.enabledRegions = -1
}
AutoPurchaseSettingsView.prototype = new BaseView;
AutoPurchaseSettingsView.prototype.initializeView = function () {
    this.enabledRegions = -1;
    this.autoPanCheckbox = Page.getElement("autoPanCheckbox");
    this.manualModeCheckbox = Page.getElement("manualModeCheckbox");
    this.automaticModeCheckbox = Page.getElement("automaticModeCheckbox");
    this.prestigeModeCheckbox = Page.getElement("prestigeModeCheckbox");
    this.columnModeCheckbox = Page.getElement("columnModeCheckbox");
    this.rowModeCheckbox = Page.getElement("rowModeCheckbox");
    this.singleRegionModeCheckbox = Page.getElement("singleRegionModeCheckbox");
    this.enabledRegionsSpan = Page.getElement("enabledRegions");
    var thisRef = this;
    this.autoPanCheckbox.onclick = function () {
        Game.autoPurchaseFollower.setAutoPanEnabled(!Game.autoPurchaseFollower.isAutoPanEnabled());
        thisRef.setAutoPan(Game.autoPurchaseFollower.isAutoPanEnabled());
        return false
    };
    this.manualModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        return false
    };
    this.automaticModeCheckbox.onclick =
        function () {
            Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.AUTOMATIC);
            thisRef.setAutoPurchaseMode(AutoPurchaseMode.AUTOMATIC);
            return false
        };
    this.prestigeModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.PRESTIGE_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.PRESTIGE_FOCUS);
        return false
    };
    this.columnModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.COLUMN_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.COLUMN_FOCUS);
        return false
    };
    this.rowModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.ROW_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.ROW_FOCUS);
        return false
    };
    this.singleRegionModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.SINGLE);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.SINGLE);
        return false
    };
    Page.getElement("disableAutoPurchasingInAllRegions").onclick = function () {
        var autoPurchaseMode = Game.autoPurchaseSettings.getAutoPurchaseMode();
        if (autoPurchaseMode != AutoPurchaseMode.MANUAL && autoPurchaseMode != AutoPurchaseMode.SINGLE) Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        Game.autoPurchaseSettings.setAutoPurchasingInAllRegions(false);
        return false
    };
    this.setAutoPurchaseMode(Game.autoPurchaseSettings.getAutoPurchaseMode());
    this.setAutoPan(Game.autoPurchaseFollower.isAutoPanEnabled())
};
AutoPurchaseSettingsView.prototype.setAutoPurchaseMode = function (autoPurchaseMode) {
    this.autoPurchaseMode = autoPurchaseMode;
    Page.getElement("manualModeImage").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.MANUAL);
    Page.getElement("automaticModeImage").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.AUTOMATIC);
    Page.getElement("prestigeModeImage").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.PRESTIGE_FOCUS);
    Page.getElement("columnModeImage").src = this.getCheckboxImage(autoPurchaseMode ==
        AutoPurchaseMode.COLUMN_FOCUS);
    Page.getElement("rowModeImage").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.ROW_FOCUS);
    Page.getElement("singleRegionModeImage").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.SINGLE)
};
AutoPurchaseSettingsView.prototype.setAutoPan = function (followModeEnabled) {
    this.autoPanEnabled = followModeEnabled;
    Page.getElement("autoPanImage").src = this.getCheckboxImage(followModeEnabled)
};
AutoPurchaseSettingsView.prototype.getCheckboxImage = function (enabled) {
    if (enabled) return "images/form/checkbox-checked.png";
    else return "images/form/checkbox.png"
};
AutoPurchaseSettingsView.prototype.updateViewContents = function () {
    var mode = Game.autoPurchaseSettings.getAutoPurchaseMode(),
        enabledRegions = Game.autoPurchaseSettings.getAutoPurchaseRegionCount(),
        autoModeEnabled = Game.autoPurchaseFollower.isAutoPanEnabled();
    if (this.autoPurchaseMode != mode) this.setAutoPurchaseMode(mode);
    if (this.autoPanEnabled != autoModeEnabled) this.setAutoPan(autoModeEnabled);
    if (this.enabledRegions != enabledRegions) {
        this.enabledRegions = enabledRegions;
        this.enabledRegionsSpan.innerHTML = Formatter.format(enabledRegions)
    }
};

function SaveControlsView() {
    this.setContainerId("saveControlsContainer");
    this.saveTimeDiv = null;
    this.saveDate = new Date;
    this.displayedSaveTime = -1
}
SaveControlsView.prototype = new BaseView;
SaveControlsView.prototype.initializeView = function () {
    var thisRef = this;
    this.saveTimeDiv = Page.getElement("lastSaveTime");
    this.displayedSaveTime = -1;
    var exportSaveButton = Page.getElement("exportSaveButton");
    exportSaveButton.onclick = function () {
        thisRef.displayExportSave();
        return false
    };
    var importSaveButton = Page.getElement("importSaveButton");
    importSaveButton.onclick = function () {
        thisRef.displayImportSave();
        return false
    };
    var closeButton = Page.getElement("closeImportExportButton");
    closeButton.onclick = function () {
        thisRef.closeImportExportContainer();
        return false
    };
    var importButtonActual = Page.getElement("importButtonActual");
    importButtonActual.onclick = function () {
        thisRef.handleImportSave();
        return false
    };
    var resetGameButton = Page.getElement("resetGameButton");
    resetGameButton.onclick = function () {
        thisRef.displayResetConfirmation();
        return false
    };
    var deleteGameButton = Page.getElement("deleteSaveButton");
    deleteGameButton.onclick = function () {
        thisRef.displayDeleteConfirmation();
        return false
    };
    var resetButtonActual = Page.getElement("resetButtonActual");
    resetButtonActual.onclick =
        function () {
            thisRef.handleGameReset();
            return false
        };
    var deleteButtonActual = Page.getElement("deleteButtonActual");
    deleteButtonActual.onclick = function () {
        thisRef.handleGameDelete();
        return false
    };
    var closeResetButton = Page.getElement("closeResetButton");
    closeResetButton.onclick = function () {
        thisRef.closeResetConfirmation();
        return false
    };
    var closeDeleteButton = Page.getElement("closeDeleteButton");
    closeDeleteButton.onclick = function () {
        thisRef.closeDeleteConfirmation();
        return false
    }
};
SaveControlsView.prototype.displayExportSave = function () {
    Page.hideElementId("saveButtonsContainer");
    Page.showElementId("importExportContainer");
    Page.hideElementId("importButtonActual");
    var exportTextArea = Page.getElement("importExportTextArea");
    exportTextArea.value = Game.saveManager.getSaveState()
};
SaveControlsView.prototype.displayImportSave = function () {
    Page.hideElementId("saveButtonsContainer");
    Page.showElementId("importExportContainer");
    Page.showElementId("importButtonActual");
    Page.hideElementId("importResults")
};
SaveControlsView.prototype.closeImportExportContainer = function () {
    var exportTextArea = Page.getElement("importExportTextArea");
    exportTextArea.value = "";
    Page.hideElementId("importResults");
    Page.hideElementId("importExportContainer");
    Page.showElementId("saveButtonsContainer")
};
SaveControlsView.prototype.handleImportSave = function () {
    var currentState = Game.saveManager.getSaveState();
    var saveState = Page.getElement("importExportTextArea").value;
    var importResults = Page.getElement("importResults");
    Page.showElement(importResults);
    if (Game.saveManager.importSave(saveState)) {
        importResults.innerHTML = "Import successful.";
        Game.gameView.onGameReset()
    } else {
        importResults.innerHTML = "Error importing save.";
        Game.saveManager.importSave(currentState)
    }
};
SaveControlsView.prototype.handleGameReset = function () {
    this.internalDeleteReset(false);
    this.closeResetConfirmation()
};
SaveControlsView.prototype.handleGameDelete = function () {
    this.internalDeleteReset(true);
    this.closeDeleteConfirmation()
};
SaveControlsView.prototype.internalDeleteReset = function (deleteSave) {
    if (deleteSave) Game.total.setVictories(0);
    Game.initializeAll();
    Game.saveManager.deleteSave();
    Game.saveManager.saveGame();
    Game.onSaveLoad();
    Game.gameView.onGameReset()
};
SaveControlsView.prototype.displayResetConfirmation = function () {
    Page.hideElementId("saveButtonsContainer");
    Page.showElementId("resetConfirmationContainer")
};
SaveControlsView.prototype.displayDeleteConfirmation = function () {
    Page.hideElementId("saveButtonsContainer");
    Page.showElementId("deleteConfirmationContainer")
};
SaveControlsView.prototype.closeResetConfirmation = function () {
    Page.hideElementId("resetConfirmationContainer");
    Page.showElementId("saveButtonsContainer")
};
SaveControlsView.prototype.closeDeleteConfirmation = function () {
    Page.hideElementId("deleteConfirmationContainer");
    Page.showElementId("saveButtonsContainer")
};
SaveControlsView.prototype.updateViewContents = function () {
    var saveTime = Game.saveManager.getLastSaveTime();
    if (this.displayedSaveTime != saveTime) {
        this.displayedSaveTime = saveTime;
        this.saveDate.setTime(this.displayedSaveTime);
        this.saveTimeDiv.innerHTML = "上次保存时间: " + this.saveDate.toLocaleTimeString()
    }
};

function MenuContentsView() {
    this.setContainerId("bottomMenuContents");
    this.setViewVisible(false);
    this.addView(new NumbersView);
    this.addView(new AutoPurchaseSettingsView);
    this.addView(new SaveControlsView)
}
MenuContentsView.prototype = new ParentView;

function MenuView() {
    this.setContainerId("bottomMenu");
    this.menuContentsView = new MenuContentsView;
    this.menuOpen = false;
    this.addView(new MenuHeaderView);
    this.addView(this.menuContentsView)
}
MenuView.prototype = new ParentView;
MenuView.prototype.initializeView = function () {
    this.invokeInitializeViewOnChildren();
    var thisRef = this;
    var bottomMenu = Page.getElement("bottomMenuHeader");
    bottomMenu.onclick = function () {
        var menu = document.getElementById("bottomMenu");
        if (thisRef.menuOpen) menu.className = "menu-closed";
        else menu.className = "menu-open";
        thisRef.menuOpen = !thisRef.menuOpen;
        thisRef.menuContentsView.setViewVisible(!thisRef.menuContentsView.isViewVisible())
    }
};

function ModeView() {
    this.setContainerId("modeMenuContents");
    this.setViewVisible(false);
    this.autoPanCheckbox = null;
    this.autoPanEnabled = false;
    this.manualModeCheckbox = null;
    this.automaticModeCheckbox = null;
    this.prestigeModeCheckbox = null;
    this.columnModeCheckbox = null;
    this.rowModeCheckbox = null;
    this.singleRegionModeCheckbox = null;
    this.autoPurchaseMode = AutoPurchaseMode.MANUAL
}
ModeView.prototype = new BaseView;
ModeView.prototype.initializeView = function () {
    this.manualModeCheckbox = Page.getElement("manualModeCheckbox2");
    this.automaticModeCheckbox = Page.getElement("automaticModeCheckbox2");
    this.prestigeModeCheckbox = Page.getElement("prestigeModeCheckbox2");
    this.columnModeCheckbox = Page.getElement("columnModeCheckbox2");
    this.rowModeCheckbox = Page.getElement("rowModeCheckbox2");
    this.singleRegionModeCheckbox = Page.getElement("singleRegionModeCheckbox2");
    this.autoPanCheckbox = Page.getElement("autoPanCheckbox2");
    var thisRef =
        this;
    this.manualModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        return false
    };
    this.automaticModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.AUTOMATIC);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.AUTOMATIC);
        return false
    };
    this.prestigeModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.PRESTIGE_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.PRESTIGE_FOCUS);
        return false
    };
    this.columnModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.COLUMN_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.COLUMN_FOCUS);
        return false
    };
    this.rowModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.ROW_FOCUS);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.ROW_FOCUS);
        return false
    };
    this.singleRegionModeCheckbox.onclick = function () {
        Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.SINGLE);
        thisRef.setAutoPurchaseMode(AutoPurchaseMode.SINGLE);
        return false
    };
    this.autoPanCheckbox.onclick = function () {
        Game.autoPurchaseFollower.setAutoPanEnabled(!Game.autoPurchaseFollower.isAutoPanEnabled());
        thisRef.setAutoPan(Game.autoPurchaseFollower.isAutoPanEnabled());
        return false
    };
    Page.getElement("disableAutoPurchasingInAllRegions").onclick = function () {
        var autoPurchaseMode = Game.autoPurchaseSettings.getAutoPurchaseMode();
        if (autoPurchaseMode != AutoPurchaseMode.MANUAL && autoPurchaseMode != AutoPurchaseMode.SINGLE) Game.autoPurchaseSettings.setAutoPurchaseMode(AutoPurchaseMode.MANUAL);
        Game.autoPurchaseSettings.setAutoPurchasingInAllRegions(false);
        return false
    };
    this.setAutoPurchaseMode(Game.autoPurchaseSettings.getAutoPurchaseMode());
    this.setAutoPan(Game.autoPurchaseFollower.isAutoPanEnabled())
};
ModeView.prototype.setAutoPurchaseMode = function (autoPurchaseMode) {
    this.autoPurchaseMode = autoPurchaseMode;
    Page.getElement("manualModeImage2").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.MANUAL);
    Page.getElement("automaticModeImage2").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.AUTOMATIC);
    Page.getElement("prestigeModeImage2").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.PRESTIGE_FOCUS);
    Page.getElement("columnModeImage2").src = this.getCheckboxImage(autoPurchaseMode ==
        AutoPurchaseMode.COLUMN_FOCUS);
    Page.getElement("rowModeImage2").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.ROW_FOCUS);
    Page.getElement("singleRegionModeImage2").src = this.getCheckboxImage(autoPurchaseMode == AutoPurchaseMode.SINGLE)
};
ModeView.prototype.setAutoPan = function (followModeEnabled) {
    this.autoPanEnabled = followModeEnabled;
    Page.getElement("autoPanImage2").src = this.getCheckboxImage(followModeEnabled)
};
ModeView.prototype.getCheckboxImage = function (enabled) {
    if (enabled) return "images/form/checkbox-checked.png";
    else return "images/form/checkbox.png"
};
ModeView.prototype.updateViewContents = function () {
    var mode = Game.autoPurchaseSettings.getAutoPurchaseMode(),
        autoModeEnabled = Game.autoPurchaseFollower.isAutoPanEnabled();
    if (this.autoPurchaseMode != mode) this.setAutoPurchaseMode(mode);
    if (this.autoPanEnabled != autoModeEnabled) this.setAutoPan(autoModeEnabled)
};

function ModeHeaderView() {
    this.setContainerId("modeMenuHeader");
    this.currentModeNameDiv = null;
    this.autoPurchaseMode = AutoPurchaseMode.MANUAL
}
ModeHeaderView.prototype = new BaseView;
ModeHeaderView.prototype.initializeView = function () {
    this.currentModeNameDiv = Page.getElement("currentModeName");
    this.setAutoPurchaseMode(Game.autoPurchaseSettings.getAutoPurchaseMode())
};
ModeHeaderView.prototype.setAutoPurchaseMode = function (autoPurchaseMode) {
    this.autoPurchaseMode = autoPurchaseMode;
    this.currentModeNameDiv.innerHTML = this.getModeName(autoPurchaseMode)
};
ModeHeaderView.prototype.updateViewContents = function () {
    var mode = Game.autoPurchaseSettings.getAutoPurchaseMode();
    if (this.autoPurchaseMode != mode) this.setAutoPurchaseMode(mode)
};
ModeHeaderView.prototype.getModeName = function (mode) {
    switch (mode) {
        case AutoPurchaseMode.MANUAL:
            return "手动模式";
        case AutoPurchaseMode.AUTOMATIC:
            return "自动模式";
        case AutoPurchaseMode.PRESTIGE_FOCUS:
            return "声望模式";
        case AutoPurchaseMode.COLUMN_FOCUS:
            return "列模式";
        case AutoPurchaseMode.ROW_FOCUS:
            return "行模式";
        case AutoPurchaseMode.SINGLE:
            return "单个模式";
        default:
            return "手动模式"
    }
};

function ModeMenuView() {
    this.setContainerId("modeMenu");
    this.modeView = new ModeView;
    this.menuOpen = false;
    this.addView(new ModeHeaderView);
    this.addView(this.modeView)
}
ModeMenuView.prototype = new ParentView;
ModeMenuView.prototype.initializeView = function () {
    this.invokeInitializeViewOnChildren();
    var thisRef = this;
    var bottomMenu = Page.getElement("modeMenuHeader");
    bottomMenu.onclick = function () {
        var menu = document.getElementById("modeMenu");
        if (thisRef.menuOpen) menu.className = "mode-menu-closed";
        else menu.className = "mode-menu-open";
        thisRef.menuOpen = !thisRef.menuOpen;
        thisRef.modeView.setViewVisible(!thisRef.modeView.isViewVisible())
    }
};

function CanvasView() {
    this.setContainerId("gameCanvas");
    this.canvas = null;
    this.context = null
}
CanvasView.prototype = new BaseView;
CanvasView.prototype.initializeView = function () {
    this.canvas = Page.getElement("gameCanvas");
    this.context = this.canvas.getContext("2d")
};
CanvasView.prototype.updateViewContents = function () {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.context.font = "10pt helvetica";
    this.renderGrid()
};
CanvasView.prototype.renderGrid = function () {
    var zones = Game.grid.getGridZones(),
        i;
    for (i = 0; i < zones.length; i++)
        if (zones[i].isZoneUnlocked()) this.renderZone(zones[i])
};
CanvasView.prototype.renderZone = function (grid) {
    var context = this.context,
        regions = grid.getGridRegions(),
        region, i;
    var projection = Game.projection,
        visibleHalfWidth = projection.screenToWorldDistance(Game.canvas.width / 2),
        visibleHalfHeight = projection.screenToWorldDistance(Game.canvas.height / 2),
        gridCenter = projection.getGridCenter();
    if (grid.isZoneVisible(gridCenter, visibleHalfWidth, visibleHalfHeight)) {
        context.save();
        if (Game.gridRepresentation.isZoomedOutPastRegions()) {
            grid.renderGridBackground(context);
            this.setupWorldToScreen();
            grid.renderZone(context)
        } else {
            var zoomedOutPastCells = Game.regionRepresentation.isZoomedOutPastCells();
            for (i = 0; i < regions.length; i++) {
                region = regions[i];
                if (region.isRegionVisible(gridCenter, visibleHalfWidth, visibleHalfHeight)) {
                    if (!zoomedOutPastCells) {
                        Game.virtualRegion.setRegion(region, false);
                        Game.virtualRegion.renderCellBordersAndBackgrounds(context)
                    } else region.renderRegionBackground(context);
                    context.save();
                    this.setupWorldToScreen();
                    region.renderRegion(context);
                    context.restore()
                }
            }
        }
        context.restore()
    }
};
CanvasView.prototype.setupWorldToScreen = function () {
    var projection = Game.projection,
        screenOrigin = projection.getScreenOrigin(),
        context = this.context,
        zoom = projection.getZoom();
    context.translate(-screenOrigin.x, -screenOrigin.y);
    context.scale(zoom, zoom)
};

function GridView() {
    this.setContainerId("gridContainer");
    this.addView(new CanvasView)
}
GridView.prototype = new ParentView;

function GameView() {
    this.setContainerId("gameContainer")
}
GameView.prototype = new ParentView;
GameView.prototype.createChildren = function () {
    this.clearChildViews();
    this.addView(new MenuView);
    this.addView(new ModeMenuView);
    this.addView(new GridView);
    this.addView(new VictoryView)
};
GameView.prototype.onGameReset = function () {
    this.invokeInitializeViewOnChildren()
};

function VictoryView() {
    this.setContainerId("victoryContainer");
    this.displayedVictory = false
}
VictoryView.prototype = new BaseView;
VictoryView.prototype.initializeView = function () {
    this.displayedVictory = false
};
VictoryView.prototype.isViewVisible = function () {
    return Game.gameWon
};
VictoryView.prototype.updateViewContents = function () {
    var victory = Game.gameWon;
    if (this.displayedVictory != victory) {
        if (victory) this.updateVictoryContents();
        else Page.hideElementId("victoryContainer");
        this.displayedVictory = victory
    }
};
VictoryView.prototype.updateVictoryContents = function () {
    var numCellsPerRegion = Settings.region.numCellColsInRegion * Settings.region.numCellRowsInRegion,
        numRegionsPerZone = Settings.region.numRegionColsInZone * Settings.region.numRegionRowsInZone,
        numZones = Game.grid.getGridZones().length,
        gridCellCount = numZones * numRegionsPerZone * numCellsPerRegion;
    var victoryTitleDiv = Page.getElement("victoryTitle");
    victoryTitleDiv.innerHTML = "胜利!";
    var victoryComments = Page.getElement("victoryComments");
    victoryComments.innerHTML =
        "你已经填满了全部 " + gridCellCount + " 个网格!";
    var timeDisplay = Page.getElement("victoryTime");
    timeDisplay.innerHTML = "耗时: " + Formatter.formatElapsedTime(Game.total.getGameEndTime() - Game.total.getGameStartTime());
    var restartButton = Page.getElement("restartButton");
    restartButton.innerHTML = "重新开始";
    restartButton.onmouseup = function () {
        Game.onGamePrestige()
    };
    var restartComments = Page.getElement("restartComments");
    restartComments.innerHTML = "重新开始会让网格变得更大";
    Page.showElementId("victoryContainer")
};

function GameLoop() {
    window.requestAnimFrame = function () {
        return window.requestAnimationFrame || (window.webkitRequestAnimationFrame || (window.mozRequestAnimationFrame || (window.oRequestAnimationFrame || (window.msRequestAnimationFrame || function (callback) {
            window.setTimeout(callback, 1E3 / 60)
        }))))
    }();
    var selfReference = this;
    this.gameLoopReference = function () {
        selfReference.gameLoop()
    }
}
GameLoop.prototype.gameLoop = function () {
    if (!Game.gameInitialized) {
        Game.gameInitialized = true;
        Game.initializeAll();
        Game.saveManager.loadSave();
        Game.gameView.createChildren();
        Game.gameView.initializeView()
    } else {
        Game.inputHandler.handleInput();
        Game.projection.updateProjectionForFrame();
        Game.worldMouse.updateWorldMouseForFrame();
        var elapsedTicks = Game.tick.getElapsedTicks();
        if (elapsedTicks > 0) {
            Game.total.updateForFrame(elapsedTicks);
            Game.autoPurchase.makePurchasesForFrame()
        }
        Game.autoPurchaseFollower.updateFollowerForFrame();
        if (Game.gameVisible) Game.gameView.updateView();
        Game.userInput.resetInputState();
        Game.saveManager.checkForAutoSave()
    }
    window.requestAnimFrame(this.gameLoopReference)
};
var Game = {
    canvas: null,
    gameVisible: true,
    gameInitialized: false,
    gameLoop: new GameLoop,
    gameView: new GameView,
    saveManager: new SaveManager,
    gameWon: false,
    tick: new Tick,
    total: new Total,
    cellRepresentation: new CellRepresentation,
    regionRepresentation: new RegionRepresentation,
    gridRepresentation: new ZoneRepresentation,
    grid: new Grid,
    virtualRegion: new VirtualRegion,
    projection: new Projection,
    autoPurchase: new AutoPurchase,
    autoPurchaseFollower: new AutoPurchaseFollower,
    userInput: new UserInput,
    worldMouse: new WorldMouse,
    inputHandler: new DelegatingInputHandler,
    autoPurchaseSettings: new AutoPurchaseSettings,
    onVisibilityChange: function () {
        if (typeof document.hidden !== "undefined") Game.gameVisible = !document.hidden
    },
    setupVisibilityListener: function () {
        var onVisibilityChange = Game.onVisibilityChange;
        document.addEventListener("visibilitychange", onVisibilityChange, false)
    },
    initGame: function (document) {
        Game.canvas = document.getElementById("gameCanvas");
        Game.puzzleCanvas = document.getElementById("researchGrid")
    },
    onSaveLoad: function () {
        Game.grid.onSaveLoad();
        Game.tick.onSaveLoad();
        Game.total.onSaveLoad();
        Game.autoPurchaseSettings.onSaveLoad();
        Game.autoPurchase.onSaveLoad()
    },
    initializeAll: function () {
        Game.gameWon = false;
        Game.projection.resetProjectionState();
        Game.autoPurchaseSettings.resetSettings();
        Game.autoPurchase.resetState();
        Game.autoPurchaseFollower.resetFollower();
        Game.tick.resetState();
        Game.grid.resetState();
        Game.total.resetState();
        Game.inputHandler.resetInputHandler()
    },
    onResize: function (window) {
        Game.canvas.width = window.innerWidth;
        Game.canvas.height =
            window.innerHeight
    },
    onLoad: function (window, document) {
        Game.canvas.width = window.innerWidth;
        Game.canvas.height = window.innerHeight;
        Game.userInput.attachListeners(document, Game.canvas);
        Game.canvas.focus();
        Game.setupVisibilityListener();
        EventTracker.trackEvent("BASIC2", "Start");
        Game.gameLoop.gameLoop()
    },
    onGameCompletion: function () {
        Game.gameWon = true;
        Game.total.onGameVictory();
        Game.saveManager.saveGame();
        EventTracker.trackEvent("BASIC2 Victory", "Victories: " + Game.total.getVictories())
    },
    onGamePrestige: function () {
        Game.initializeAll();
        Game.saveManager.deleteSave();
        Game.saveManager.saveGame();
        Game.onSaveLoad();
        Game.gameView.onGameReset()
    }
};
